{"record":{"id":"c3bc9b7a3638afad","repo":"microsoft/aspire","slug":"a-circular-lifetime-reference-was-detected-for-resource","errorCode":null,"errorMessage":"A circular lifetime reference was detected for resource '{resource.Name}'.","messagePattern":"A circular lifetime reference was detected for resource '(.+?)'\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs","lineNumber":1117,"sourceCode":"    /// <summary>\n    /// Gets the lifetime type for the specified resource.\n    /// Defaults to <see cref=\"Lifetime.Session\"/> if no lifetime annotation is found.\n    /// </summary>\n    /// <param name=\"resource\">The resource to get the lifetime type for.</param>\n    /// <returns>\n    /// The <see cref=\"Lifetime\"/> from the <see cref=\"PersistenceAnnotation\"/> for the resource (if the annotation exists).\n    /// Defaults to <see cref=\"Lifetime.Session\"/> if the annotation is not set.\n    /// </returns>\n    internal static Lifetime GetLifetimeType(this IResource resource)\n    {\n        return GetLifetimeType(resource, []);\n    }\n\n    private static Lifetime GetLifetimeType(IResource resource, HashSet<IResource> visitedResources)\n    {\n        if (!visitedResources.Add(resource))\n        {\n            throw new InvalidOperationException($\"A circular lifetime reference was detected for resource '{resource.Name}'.\");\n        }\n\n        if (resource.TryGetLastAnnotation<PersistenceAnnotation>(out var persistenceAnnotation))\n        {\n            return persistenceAnnotation.Mode switch\n            {\n                PersistenceMode.Session => Lifetime.Session,\n                PersistenceMode.Persistent => Lifetime.Persistent,\n                PersistenceMode.Resource => persistenceAnnotation.SourceResource is { } sourceResource\n                    ? GetLifetimeType(sourceResource, visitedResources)\n                    : throw new InvalidOperationException($\"Resource '{resource.Name}' has a resource persistence mode but no source resource.\"),\n                PersistenceMode.ParentProcess => Lifetime.Persistent,\n                _ => throw new InvalidOperationException($\"Unknown persistence mode '{Enum.GetName(typeof(PersistenceMode), persistenceAnnotation.Mode)}'.\")\n            };\n        }\n\n        if (resource.TryGetLastAnnotation<ContainerLifetimeAnnotation>(out var containerLifetimeAnnotation))\n        {","sourceCodeStart":1099,"sourceCodeEnd":1135,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs#L1099-L1135","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\ncache.WithPersistence(PersistenceMode.Resource, source: db);\ndb.WithPersistence(PersistenceMode.Resource, source: cache); // cycle\n// after\ncache.WithPersistence(PersistenceMode.Resource, source: db);\ndb.WithPersistence(PersistenceMode.Persistent); // root resource uses a concrete mode","handlingStrategy":"validation","validationCode":"// Walk persistence sources and detect repeats before startup\nvar visited = new HashSet<IResource>();\nfor (var r = resource; r.TryGetLastAnnotation<PersistenceAnnotation>(out var pa) && pa.Mode == PersistenceMode.Resource && pa.SourceResource is { } src; r = src)\n    if (!visited.Add(r)) throw new InvalidOperationException($\"Circular persistence chain at {r.Name}\");","typeGuard":null,"tryCatchPattern":"try { var lifetime = resource.GetLifetimeType(); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"circular lifetime\")) { /* surface model error with resource names */ throw; }","preventionTips":["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."],"tags":["aspire","persistence","circular-reference","model-validation"],"backgroundTag":"internal-invariant-violation","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}