{"record":{"id":"0092f91981ee8c7f","repo":"microsoft/aspire","slug":"radius-resource-credential-consumer-bicepidentifier-reads","errorCode":null,"errorMessage":"Radius resource '{credential.Consumer.BicepIdentifier}' reads its '{credential.PropertyName}' from key '{credential.SecretKey}' of the '{RadiusResourceTypes.SecuritySecrets}' resource '{credential.Secret.BicepIdentifier}', but a ConfigureRadiusInfrastructure callback removed that key.","messagePattern":"Radius resource '(.+?)' reads its '(.+?)' from key '(.+?)' of the '(.+?)' resource '(.+?)', but a ConfigureRadiusInfrastructure callback removed that key\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs","lineNumber":964,"sourceCode":"                throw new InvalidOperationException(\n                    $\"Radius resource '{credential.Consumer.BicepIdentifier}' reads its '{credential.PropertyName}' from \" +\n                    $\"the '{RadiusResourceTypes.SecuritySecrets}' resource '{credential.OriginalSecretIdentifier}', but a \" +\n                    $\"ConfigureRadiusInfrastructure callback removed it. The property is required, so the deployment would \" +\n                    $\"be rejected. Keep the secret, or point '{credential.PropertyName}' at a secret of your own. \" +\n                    $\"Diagnostic: ASPIRERADIUS074.\");\n            }\n\n            // The consumer still reads this secret, so the entry carrying the credential has to\n            // survive intact. Unlike a container env secret — whose only reader is the variable that\n            // points at it, so a callback replacing the value is self-consistent — this value is\n            // handed to the *recipe* that provisions the server, while the matching credential was\n            // already composed into every consumer's connection string from Aspire's own parameter.\n            // Removing it prevents the recipe from starting; changing it provisions a server with a\n            // password no consumer was told about, which fails only as an authentication error at\n            // runtime. Neither can be repaired here, so both are rejected.\n            if (!credential.Secret.Data.TryGetValue(credential.SecretKey, out var liveEntry))\n            {\n                throw new InvalidOperationException(\n                    $\"Radius resource '{credential.Consumer.BicepIdentifier}' reads its '{credential.PropertyName}' from \" +\n                    $\"key '{credential.SecretKey}' of the '{RadiusResourceTypes.SecuritySecrets}' resource \" +\n                    $\"'{credential.Secret.BicepIdentifier}', but a ConfigureRadiusInfrastructure callback removed that \" +\n                    $\"key. The recipe cannot provision the resource without it. Keep the key, or point \" +\n                    $\"'{credential.PropertyName}' at a secret of your own. Diagnostic: ASPIRERADIUS089.\");\n            }\n\n            // Both an entry swapped for a new construct and one mutated in place are rejected: the\n            // credential Aspire projected to consumers is fixed at this point either way. The\n            // encoding is checked alongside the value because it decides how the recipe interprets\n            // that value — flipping `string` to `base64` makes the recipe decode before writing the\n            // Kubernetes Secret, so the provisioned credential diverges from the one consumers hold\n            // even though the value is byte-identical.\n            if (!ReferenceEquals(liveEntry?.Value, credential.Entry) ||\n                !string.Equals(RenderBicepValue(credential.Entry.Value), credential.OriginalEntryValue, StringComparison.Ordinal) ||\n                !string.Equals(RenderBicepValue(credential.Entry.Encoding), credential.OriginalEntryEncoding, StringComparison.Ordinal))\n            {\n                throw new InvalidOperationException(","sourceCodeStart":946,"sourceCodeEnd":982,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs#L946-L982","documentation":"After running ConfigureRadiusInfrastructure callbacks, the builder validates that each secret key consumed as a required credential property still exists in the secret's Data. If a callback removed the key (or the whole entry), the recipe cannot provision the resource, so this InvalidOperationException with ASPIRERADIUS089 is thrown.","triggerScenarios":"A ConfigureRadiusInfrastructure callback mutates a generated SecuritySecrets resource and deletes the specific Data key (credential.SecretKey) that a Radius resource's required property (credential.PropertyName) references.","commonSituations":"Callback code rebuilding the secret's Data dictionary and dropping entries; removing a password/username key it assumed was unused; renames of keys that don't update the consumer references.","solutions":["Keep the referenced key in the secret's Data dictionary in your callback.","Instead of removing the key, point the consuming '{PropertyName}' at your own secret in the callback.","Audit callbacks that clear or rebuild secret Data so credential keys are preserved."],"exampleFix":"// before\nsecret.Data.Remove(\"password\"); // consumer still references it\n\n// after\n// keep the key, or repoint the consumer property to your own secret resource","handlingStrategy":"validation","validationCode":"// Before saving secret mutations in a callback, verify required keys remain\nvar requiredKeys = new HashSet<string> { \"password\", \"username\" }; // keys consumers reference\nvar missing = requiredKeys.Except(secret.Data.Keys).ToList();\nif (missing.Count > 0)\n    throw new InvalidOperationException($\"Cannot remove credential keys: {string.Join(',', missing)} are referenced by consumers.\");","typeGuard":null,"tryCatchPattern":"try { await builder.ExecuteCallbacksAsync(ct); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"ASPIRERADIUS089\") && ex.Message.Contains(\"removed that key\"))\n{\n    logger.LogError(ex, \"Callback deleted a consumed secret key; keep it or repoint the consumer.\");\n}","preventionTips":["Append to or add new keys in secret Data rather than removing existing ones","Treat generated secret Data entries as read-only in callbacks","When rebuilding Data dictionaries, spread the original entries first: Data = new(original) { ... }"],"tags":["radius","secrets","callback","diagnostic-aspireradius089","bicep"],"backgroundTag":"missing-config-key","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}