microsoft/aspire · error · InvalidOperationException
Azure sandbox resources
Error message
Azure sandbox resources '{resource.TargetResource.Name}' and '{producer.TargetResource.Name}' have a circular deployment dependency. What it means
When wiring deploy-order dependencies between sandbox resources, adding a consumer-step dependency on a producer-step would create a cycle in the pipeline step graph. Aspire detects this with WouldCreateDependencyCycle and throws InvalidOperationException instead of producing a deadlocked deployment order.
Solutions
- Break the cycle: remove one WithReference so the dependency graph is acyclic.
- Extract the shared value (e.g. an endpoint or connection string) into an explicitly configured parameter instead of mutual resource references.
- Restructure the app so the dependency is one-directional (producer consumed by consumer only).
Example fix
// before api.WithReference(worker); worker.WithReference(api); // cycle // after api.WithReference(worker); // worker no longer references api; pass api's URL as config
Defensive patterns
Strategy: validation
Validate before calling
// before deploy: detect reference cycles in the sandbox resource graph
bool HasCycle(Dictionary<string, string[]> edges) =>
// DFS with visit states; report any node reachable from itself Try / catch
try { await deployment.ConfigureDeployOrderingAsync(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("circular deployment dependency")) { /* break the WithReference cycle flagged in the message */ } Prevention
- Keep resource dependencies one-directional; never A.WithReference(B) plus B.WithReference(A).
- Extract shared endpoints into parameters when two services need each other.
- Sketch the dependency DAG before adding references.
When it happens
Trigger: Two sandbox resources reference each other's outputs (A references B and B references A), or an indirect cycle exists across several WithReference calls, so consumerStep.DependsOn(producerStep) would close a loop.
Common situations: Mutually dependent services (each calling the other and both modeled with WithReference), or a refactoring that accidentally reversed a dependency direction.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ADC request ' ' returned an incomplete response.
- Azure sandbox resource
- exceeds the maximum supported ADC interval.
- must be a non-negative whole-second duration.
- Sandbox disk image ' ' was not ready after seconds (last…
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/3f982a51a9a68220.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.Azure.Sandboxes/AzureSandboxContainerDeployment.cs:180
if (dependency.GetDeploymentTargetAnnotation()?.DeploymentTarget is not AzureSandboxContainerResource producer)
{
continue;
}
if (!ReferenceEquals(producer.Parent, resource.Parent))
{
throw new NotSupportedException(
$"Azure sandbox resource '{resource.TargetResource.Name}' in sandbox group '{resource.Parent.Name}' references resource '{dependency.Name}' in sandbox group '{producer.Parent.Name}', but cross-group sandbox references are not supported. Deploy both resources to the same sandbox group.");
}
var producerSteps = context.GetSteps(producer, WellKnownPipelineTags.DeployCompute);
foreach (var consumerStep in deploySteps)
{
foreach (var producerStep in producerSteps)
{
if (WouldCreateDependencyCycle(context.Steps, consumerStep, producerStep))
{
throw new InvalidOperationException(
$"Azure sandbox resources '{resource.TargetResource.Name}' and '{producer.TargetResource.Name}' have a circular deployment dependency.");
}
consumerStep.DependsOn(producerStep);
}
}
}
}
private static IEnumerable<PipelineStep> GetAzureEnvironmentDestroySteps(PipelineConfigurationContext context)
{
foreach (var environment in context.Model.Resources.OfType<AzureEnvironmentResource>())
{
var expectedName = $"destroy-azure-{environment.Name}";
foreach (var step in context.GetSteps(environment).Where(step => string.Equals(step.Name, expectedName, StringComparison.Ordinal)))
{
yield return step;
}View on GitHub (pinned to 25830f84bd)