microsoft/aspire · error · InvalidOperationException

A circular lifetime reference was detected for resource

Error message

A circular lifetime reference was detected for resource '{resource.Name}'.

What it means

GetLifetimeType resolves a resource's effective Lifetime by following PersistenceAnnotation.SourceResource references. A HashSet of visited resources guards against cycles; if a resource is already visited, the model contains a circular persistence chain, and the method throws InvalidOperationException instead of recursing forever.

Solutions

  1. Break the cycle: make the persistence source of one resource a resource that does not (transitively) reference it back.
  2. Point persistence sources at true root resources (e.g. the original volume/database resource).
  3. Audit WithPersistence / PersistenceAnnotation.SourceResource assignments for accidental self or mutual references.

Example fix

// before
cache.WithPersistence(PersistenceMode.Resource, source: db);
db.WithPersistence(PersistenceMode.Resource, source: cache); // cycle
// after
cache.WithPersistence(PersistenceMode.Resource, source: db);
db.WithPersistence(PersistenceMode.Persistent); // root resource uses a concrete mode
Defensive patterns

Strategy: validation

Validate before calling

// Walk persistence sources and detect repeats before startup
var visited = new HashSet<IResource>();
for (var r = resource; r.TryGetLastAnnotation<PersistenceAnnotation>(out var pa) && pa.Mode == PersistenceMode.Resource && pa.SourceResource is { } src; r = src)
    if (!visited.Add(r)) throw new InvalidOperationException($"Circular persistence chain at {r.Name}");

Try / catch

try { var lifetime = resource.GetLifetimeType(); } catch (InvalidOperationException ex) when (ex.Message.Contains("circular lifetime")) { /* surface model error with resource names */ throw; }

Prevention

When it happens

Trigger: Configuring two resources whose persistence modes reference each other (directly or transitively) — e.g. A's WithPersistence(..., source: B) and B's persistence source is A — then reading the resource's lifetime (e.g. during publish or lifetime resolution).

Common situations: Refactoring persistence wiring where a source resource is accidentally set back to an ancestor in the chain; code-generated app models that link persistence sources in a loop.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/c3bc9b7a3638afad. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs:1117

    /// <summary>
    /// Gets the lifetime type for the specified resource.
    /// Defaults to <see cref="Lifetime.Session"/> if no lifetime annotation is found.
    /// </summary>
    /// <param name="resource">The resource to get the lifetime type for.</param>
    /// <returns>
    /// The <see cref="Lifetime"/> from the <see cref="PersistenceAnnotation"/> for the resource (if the annotation exists).
    /// Defaults to <see cref="Lifetime.Session"/> if the annotation is not set.
    /// </returns>
    internal static Lifetime GetLifetimeType(this IResource resource)
    {
        return GetLifetimeType(resource, []);
    }

    private static Lifetime GetLifetimeType(IResource resource, HashSet<IResource> visitedResources)
    {
        if (!visitedResources.Add(resource))
        {
            throw new InvalidOperationException($"A circular lifetime reference was detected for resource '{resource.Name}'.");
        }

        if (resource.TryGetLastAnnotation<PersistenceAnnotation>(out var persistenceAnnotation))
        {
            return persistenceAnnotation.Mode switch
            {
                PersistenceMode.Session => Lifetime.Session,
                PersistenceMode.Persistent => Lifetime.Persistent,
                PersistenceMode.Resource => persistenceAnnotation.SourceResource is { } sourceResource
                    ? GetLifetimeType(sourceResource, visitedResources)
                    : throw new InvalidOperationException($"Resource '{resource.Name}' has a resource persistence mode but no source resource."),
                PersistenceMode.ParentProcess => Lifetime.Persistent,
                _ => throw new InvalidOperationException($"Unknown persistence mode '{Enum.GetName(typeof(PersistenceMode), persistenceAnnotation.Mode)}'.")
            };
        }

        if (resource.TryGetLastAnnotation<ContainerLifetimeAnnotation>(out var containerLifetimeAnnotation))
        {

View on GitHub (pinned to 25830f84bd)