Skip to content

[bug] aspire describe --apphost fails to find running AppHost when path traverses a symlink (e.g. /tmp on macOS) #17618

Description

@adamint

Summary

aspire describe --apphost <path> (and other --apphost-accepting commands that go through AppHostConnectionResolver) report "No AppHost is currently running" whenever the supplied path traverses a symlink, even though the AppHost is in fact running. On macOS this triggers any time --apphost is given a path under /tmp (because /tmp is a symlink to /private/tmp), but it also affects any user-mounted or $HOME-symlinked workspace on Linux and macOS.

The AppHost-discovery path used when no --apphost flag is given already canonicalizes (ProjectLocator.UseOrFindAppHostProjectFileAsync at src/Aspire.Cli/Projects/ProjectLocator.cs:708); the explicit-flag path does not.

Severity: Medium
CLI version: 13.5.0-preview.1.26277.22 (daily)
OS: macOS (reproduced); same bug should occur anywhere a symlink is traversed (Linux mounts, $HOME symlinks, etc.)

Repro

rm -rf /tmp/aspire-D2 && mkdir -p /tmp/aspire-D2 && cd /tmp/aspire-D2
{
  echo '#:sdk Aspire.AppHost.Sdk@13.5.0-preview.1.26277.21'
  echo ''
  echo 'var builder = DistributedApplication.CreateBuilder(args);'
  echo 'builder.AddContainer("nginx", "nginx:alpine");'
  echo 'builder.Build().Run();'
} > apphost.cs

nohup aspire run > apphost.log 2>&1 &
# wait ~30s for startup

# A) symlinked /tmp path — FAILS
aspire describe --apphost /tmp/aspire-D2/apphost.cs --non-interactive --nologo
# => ❌ No AppHost is currently running for ...

# B) canonical /private/tmp path — WORKS
aspire describe --apphost /private/tmp/aspire-D2/apphost.cs --non-interactive --nologo
# => renders the resource table including the nginx container

Expected

Both invocations should resolve to the same running AppHost. The user shouldn't have to know that /tmp -> /private/tmp on macOS, or that a corporate-mounted workspace happens to be a symlink, in order for --apphost to work.

Root cause

src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs:137 uses projectFile.FullName directly to compute the backchannel socket key:

var targetPath = projectFile.FullName;
var matchingSockets = AppHostHelper.FindMatchingSockets(
    targetPath,
    executionContext.HomeDirectory.FullName);

AppHostHelper.FindMatchingSockets -> BackchannelConstants.ComputeAppHostId -> NormalizePath only handles Windows case-folding; it does not canonicalize symlinks.

Meanwhile, when the AppHost is started, ProjectLocator.UseOrFindAppHostProjectFileAsync does canonicalize before the socket is created (src/Aspire.Cli/Projects/ProjectLocator.cs:708):

var resolvedProjectPath = PathNormalizer.ResolveToFilesystemPath(projectFile.FullName);

if (!string.Equals(resolvedProjectPath, projectFile.FullName, StringComparison.Ordinal))
{
    logger.LogDebug(...);
    projectFile = new FileInfo(resolvedProjectPath);
}

So the socket is keyed off the canonical path on the producer side, but the consumer (any --apphost-using command) hashes the raw user-supplied path. They never match when a symlink is involved.

Suggested fix

Apply the same canonicalization in AppHostConnectionResolver before computing targetPath. Either reuse the snippet above or factor it into a small helper called from both sites:

// src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs ~ line 137
var targetPath = PathNormalizer.ResolveToFilesystemPath(projectFile.FullName);

This will also fix aspire stop --apphost <symlinked>, aspire exec --apphost <symlinked>, and any other command that resolves a backchannel connection from an explicit --apphost argument.

Workaround

Pass the canonical path explicitly, e.g. aspire describe --apphost "$(realpath /tmp/aspire-D2/apphost.cs)", or omit --apphost and cd into the AppHost directory.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions