WithTerminal: per-replica terminal hosts all group under a single parent in the dashboard
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
## 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` returning the *singular* parent `IResource`:
```csharp
// src/Aspire.Hosting/ApplicationModel/TerminalHostResource.cs
public sealed class TerminalHostResource : ExecutableResource, IResourceWithParent
```
The dashboard groups children by parent resource name (`CustomResourceSnapshot.cs:484`):
```csharp
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 `TerminalHostResource`s 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 `IResource`s.
- `ResourceRelationshipAnnotation` / `IResourceWithParent` 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:
1. 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
2. Modeling DCP replicas as first-class `IResource`s in the app model, which is a larger design effort.
## Repro
```csharp
var backend = builder.AddProject("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.
Contributor guide
Assessment
This issue has not been assessed yet.