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

  1. Break the cycle: remove one WithReference so the dependency graph is acyclic.
  2. Extract the shared value (e.g. an endpoint or connection string) into an explicitly configured parameter instead of mutual resource references.
  3. 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

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


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)