Skip to content

aspire add on polyglot AppHosts pinned to a non-default channel cannot offer integrations outside that channel (CommunityToolkit, AWS, ClickHouse unreachable) #17295

@radical

Description

@radical

What's broken

When a polyglot AppHost (apphost.ts / .py / .go / .java / .rs)
has aspire.config.json#channel pinning a non-default channel,
aspire add / aspire integration list / aspire integration search /
MCP list-integrations return only the integrations available
through that one channel. Every integration the user could otherwise add
via their ambient nuget.config (Implicit channel) — including
CommunityToolkit.Aspire.Hosting.*, Aspire.Hosting.AWS,
Aspire.Hosting.ClickHouse, Aspire.Hosting.Elasticsearch, etc. —
becomes invisible.

The user has no in-CLI way to see or add these packages without first
manually editing aspire.config.json to remove the channel pin.

Repro

Setup a PR-acquired install:

./eng/scripts/get-aspire-cli-pr.sh 17105 \
    --install-path /tmp/aspire-issue-val --skip-extension --skip-path
CLI=/tmp/aspire-issue-val/dogfood/pr-17105/bin/aspire

# Polyglot project pinned to the PR channel
mkdir -p /tmp/aspire-issue-val/ts-pinned
cat > /tmp/aspire-issue-val/ts-pinned/apphost.ts <<'EOF'
// stub
EOF
cat > /tmp/aspire-issue-val/ts-pinned/aspire.config.json <<'EOF'
{ "channel": "pr-17105" }
EOF

# Same project shape, no pin
mkdir -p /tmp/aspire-issue-val/ts-unpinned
cat > /tmp/aspire-issue-val/ts-unpinned/apphost.ts <<'EOF'
// stub
EOF

# How many CommunityToolkit / AWS / ClickHouse integrations does each see?
for d in ts-pinned ts-unpinned; do
  echo "=== $d ==="
  $CLI integration list --apphost /tmp/aspire-issue-val/$d/apphost.ts --format json \
    | jq '{
        total: length,
        communityToolkit: [.[] | select(.package | startswith("CommunityToolkit."))] | length,
        aws: ([.[] | select(.package == "Aspire.Hosting.AWS")] | length > 0),
        clickhouse: ([.[] | select(.package == "Aspire.Hosting.ClickHouse")] | length > 0)
      }'
done

Expected

aspire add should show the union of:

  • Every integration reachable through the user's ambient nuget.config
    (Implicit channel) — so CommunityToolkit / AWS / ClickHouse / etc.
    remain browseable.
  • Every integration exclusive to the pinned channel (e.g. PR-only
    additions not yet on nuget.org).

For any package id that exists in both, the pinned channel's version
should win (so dogfooders see the PR version, not the stable version).
That gives the dogfooder the right version for shared ids without
artificially narrowing what's browseable.

Actual

=== ts-pinned ===
{ "total": 58, "communityToolkit": 0, "aws": false, "clickhouse": false }

=== ts-unpinned ===
{ "total": 118, "communityToolkit": 51, "aws": true, "clickhouse": true }

The pin removes 60 integrations the user could otherwise add — the
entire CommunityToolkit catalog, plus standalone integrations like AWS,
ClickHouse, Elasticsearch.

Reproduces on

  • PR feat(cli): aspire doctor lists every Aspire CLI install on the machine #17105 install (13.4.0-pr.17105.g93e48e16): pinned polyglot
    returns 58 rows / 0 CommunityToolkit; unpinned returns 118 rows / 51
    CommunityToolkit.
  • Daily/dev install (13.4.0-preview.1.26269.7, installed via
    https://aka.ms/aspire/get/install.sh --quality dev): polyglot pinned
    to "staging" returns 0 rows total (staging synthesis refuses on
    daily-identity CLIs, so the single-channel filter narrows the channel
    set to nothing); unpinned returns 128 rows / 51 CommunityToolkit /
    AWS present. Even more dramatic than the PR-install case.

Why

src/Aspire.Cli/Commands/IntegrationPackageSearchService.cs:28-31:

if (!string.IsNullOrEmpty(configuredChannel))
{
    allChannels = allChannels.Where(c =>
        string.Equals(c.Name, configuredChannel, StringComparison.OrdinalIgnoreCase));
}

When configuredChannel is set (polyglot path), the channel set is
filtered down to exactly one channel by name. The Implicit channel
(name "default") is dropped because it never matches a pr-<N> /
local / staging pin.

The fix needs to keep Implicit in the channel set when a non-default
pin is configured, then rely on dedup to decide which version wins for
shared ids. That keeps off-channel discovery intact without
compromising the pin's authority over shared-id versions.

Scope

Affects:

  • aspire add, aspire integration list, aspire integration search,
    MCP list-integrations — all only for polyglot AppHosts with a
    non-default aspire.config.json#channel.

Does not affect:

Possible directions

  1. Always include Implicit alongside the configured channel. Dedup
    then layers them naturally: the configured channel wins shared ids
    (so the PR / staging version is displayed); Implicit fills in
    everything the configured channel doesn't have.
  2. Make the pin scope explicit — keep the single-channel narrowing
    but add a flag (aspire add --all-channels,
    aspire integration list --include-default) for users who want to
    browse the broader catalog without unpinning.

Option 1 mirrors how dotnet add package itself works: a pinned source
in packageSourceMapping doesn't preclude other sources from
contributing.

Notes

Related to #17225 (which channel the scaffolder writes into a new
aspire.config.json) and #17292 (filter alignment shipping with
PR #17105). This issue is about what the picker reaches for an
already-pinned polyglot project.

Counterpart: #17294 — C# AppHosts have the opposite problem
(the pin is ignored entirely, so the picker shows everything but
displays stable versions for shared ids).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions