microsoft/aspire · error · InvalidOperationException
Radius resource ' ' reads its ' ' from key ' ' of the ' '…
Error message
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. What it means
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.
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.
Example fix
// before
secret.Data.Remove("password"); // consumer still references it
// after
// keep the key, or repoint the consumer property to your own secret resource Defensive patterns
Strategy: validation
Validate before calling
// Before saving secret mutations in a callback, verify required keys remain
var requiredKeys = new HashSet<string> { "password", "username" }; // keys consumers reference
var missing = requiredKeys.Except(secret.Data.Keys).ToList();
if (missing.Count > 0)
throw new InvalidOperationException($"Cannot remove credential keys: {string.Join(',', missing)} are referenced by consumers."); Try / catch
try { await builder.ExecuteCallbacksAsync(ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("ASPIRERADIUS089") && ex.Message.Contains("removed that key"))
{
logger.LogError(ex, "Callback deleted a consumed secret key; keep it or repoint the consumer.");
} Prevention
- 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) { ... }
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- A ConfigureRadiusInfrastructure callback changed the value…
- ASPIRERADIUS074
- ASPIRERADIUS047
- ASPIRERADIUS048
- ASPIRERADIUS051
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/0092f91981ee8c7f.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs:964
throw new InvalidOperationException(
$"Radius resource '{credential.Consumer.BicepIdentifier}' reads its '{credential.PropertyName}' from " +
$"the '{RadiusResourceTypes.SecuritySecrets}' resource '{credential.OriginalSecretIdentifier}', but a " +
$"ConfigureRadiusInfrastructure callback removed it. The property is required, so the deployment would " +
$"be rejected. Keep the secret, or point '{credential.PropertyName}' at a secret of your own. " +
$"Diagnostic: ASPIRERADIUS074.");
}
// The consumer still reads this secret, so the entry carrying the credential has to
// survive intact. Unlike a container env secret — whose only reader is the variable that
// points at it, so a callback replacing the value is self-consistent — this value is
// handed to the *recipe* that provisions the server, while the matching credential was
// already composed into every consumer's connection string from Aspire's own parameter.
// Removing it prevents the recipe from starting; changing it provisions a server with a
// password no consumer was told about, which fails only as an authentication error at
// runtime. Neither can be repaired here, so both are rejected.
if (!credential.Secret.Data.TryGetValue(credential.SecretKey, out var liveEntry))
{
throw new InvalidOperationException(
$"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. The recipe cannot provision the resource without it. Keep the key, or point " +
$"'{credential.PropertyName}' at a secret of your own. Diagnostic: ASPIRERADIUS089.");
}
// Both an entry swapped for a new construct and one mutated in place are rejected: the
// credential Aspire projected to consumers is fixed at this point either way. The
// encoding is checked alongside the value because it decides how the recipe interprets
// that value — flipping `string` to `base64` makes the recipe decode before writing the
// Kubernetes Secret, so the provisioned credential diverges from the one consumers hold
// even though the value is byte-identical.
if (!ReferenceEquals(liveEntry?.Value, credential.Entry) ||
!string.Equals(RenderBicepValue(credential.Entry.Value), credential.OriginalEntryValue, StringComparison.Ordinal) ||
!string.Equals(RenderBicepValue(credential.Entry.Encoding), credential.OriginalEntryEncoding, StringComparison.Ordinal))
{
throw new InvalidOperationException(View on GitHub (pinned to 25830f84bd)