Skip to content

WithTerminal: per-replica terminal hosts all group under a single parent in the dashboard #17895

@mitchdenny

Description

@mitchdenny

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:

  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 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-app-modelIssues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplicationarea-dashboardbugtriage:bot-seenAspire triage bot has seen this issue

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions