Summary
When WithTerminal() is applied to a resource with multiple replicas, the per-replica TerminalHostResource instances ({parent}-terminalhost-0, {parent}-terminalhost-1, …) all appear nested under the single parent resource entry in the dashboard rather than being distributed one-per-replica-instance.
Surfaced during review of #17866 by @mitchdenny:
"in the case of replicas, the terminal hosts for all replicas ends up under one resource. Quick fix or systemic issue in Aspire that we should file an issue for?"
Why it happens
TerminalHostResource is correctly modeled per replica — MaterializeTerminalHosts creates replicaCount distinct resources with unique names and the proper ParentReplicaIndex. However the parent linkage uses IResourceWithParent<IResource> returning the singular parent IResource:
// src/Aspire.Hosting/ApplicationModel/TerminalHostResource.cs
public sealed class TerminalHostResource : ExecutableResource, IResourceWithParent<IResource>
The dashboard groups children by parent resource name (CustomResourceSnapshot.cs:484):
relationships.Add(new(resourceWithParent.Parent.Name, KnownRelationshipTypes.Parent));
The relationship references the model-level parent IResource, not a specific runtime replica instance. So all N TerminalHostResources end up listed as children of the same parent entry.
Why this is systemic, not a quick fix
Aspire has no first-class concept of "child of a specific replica instance":
- Replicas are DCP runtime instances, not modeled
IResources.
ResourceRelationshipAnnotation / IResourceWithParent<T> both take an IResource, which means "the model resource", not "this specific replica named myapp-abc12".
- The dashboard's parent grouping (
CustomResourceSnapshot.GetResourceSnapshot → relationship resolution by name) has no notion of replica suffix matching.
Doing this properly likely requires either:
- A
ResourceReplicaRelationshipAnnotation (or extension to ResourceRelationshipAnnotation) that carries a replica-instance discriminator (parentReplicaIndex or DCP replica name suffix), plus dashboard support for matching child-relationship targets against replica instances rather than just resource names; or
- Modeling DCP replicas as first-class
IResources in the app model, which is a larger design effort.
Repro
var backend = builder.AddProject<Projects.Api>("api")
.WithReplicas(3)
.WithTerminal(options => options.ShowTerminalHost = true);
Expected: each of api-0, api-1, api-2 shows its own nested api-terminalhost-N child.
Actual: api-terminalhost-0/1/2 all appear nested under the singular api group in the dashboard.
Workaround
None on the Aspire side currently. Users diagnosing a single replica's terminal-host can rely on aspire terminal ps + aspire terminal attach from the CLI, which is replica-aware via --replica-index.
Out of scope for #17866
This is not blocking the initial WithTerminal() ship — single-replica resources (the dominant case) behave correctly. Filing for follow-up after the broader replica-aware-child-resource design is in place.
Summary
When
WithTerminal()is applied to a resource with multiple replicas, the per-replicaTerminalHostResourceinstances ({parent}-terminalhost-0,{parent}-terminalhost-1, …) all appear nested under the single parent resource entry in the dashboard rather than being distributed one-per-replica-instance.Surfaced during review of #17866 by @mitchdenny:
Why it happens
TerminalHostResourceis correctly modeled per replica —MaterializeTerminalHostscreatesreplicaCountdistinct resources with unique names and the properParentReplicaIndex. However the parent linkage usesIResourceWithParent<IResource>returning the singular parentIResource:The dashboard groups children by parent resource name (
CustomResourceSnapshot.cs:484):The relationship references the model-level parent
IResource, not a specific runtime replica instance. So all NTerminalHostResources end up listed as children of the same parent entry.Why this is systemic, not a quick fix
Aspire has no first-class concept of "child of a specific replica instance":
IResources.ResourceRelationshipAnnotation/IResourceWithParent<T>both take anIResource, which means "the model resource", not "this specific replica namedmyapp-abc12".CustomResourceSnapshot.GetResourceSnapshot→ relationship resolution by name) has no notion of replica suffix matching.Doing this properly likely requires either:
ResourceReplicaRelationshipAnnotation(or extension toResourceRelationshipAnnotation) that carries a replica-instance discriminator (parentReplicaIndexor DCP replica name suffix), plus dashboard support for matching child-relationship targets against replica instances rather than just resource names; orIResources in the app model, which is a larger design effort.Repro
Expected: each of
api-0,api-1,api-2shows its own nestedapi-terminalhost-Nchild.Actual:
api-terminalhost-0/1/2all appear nested under the singularapigroup in the dashboard.Workaround
None on the Aspire side currently. Users diagnosing a single replica's terminal-host can rely on
aspire terminal ps+aspire terminal attachfrom the CLI, which is replica-aware via--replica-index.Out of scope for #17866
This is not blocking the initial
WithTerminal()ship — single-replica resources (the dominant case) behave correctly. Filing for follow-up after the broader replica-aware-child-resource design is in place.