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
- Break the cycle: make the persistence source of one resource a resource that does not (transitively) reference it back.
- Point persistence sources at true root resources (e.g. the original volume/database resource).
- 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
- Point persistence sources at root resources only; never at descendants.
- Keep persistence wiring in one place to make the chain auditable.
- Document the source chain when using PersistenceMode.Resource.
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
- Resource ' ' has a resource persistence mode but no source…
- Resource ' ' has a parent process persistence mode but no…
- Unknown persistence mode
- A global MCP approval policy cannot be combined with custom…
- A security rule named
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)