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.
Summary
aspire describe --apphost <path>(and other--apphost-accepting commands that go throughAppHostConnectionResolver) 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--apphostis given a path under/tmp(because/tmpis 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
--apphostflag is given already canonicalizes (ProjectLocator.UseOrFindAppHostProjectFileAsyncatsrc/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,
$HOMEsymlinks, etc.)Repro
Expected
Both invocations should resolve to the same running AppHost. The user shouldn't have to know that
/tmp -> /private/tmpon macOS, or that a corporate-mounted workspace happens to be a symlink, in order for--apphostto work.Root cause
src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs:137usesprojectFile.FullNamedirectly to compute the backchannel socket key:AppHostHelper.FindMatchingSockets->BackchannelConstants.ComputeAppHostId->NormalizePathonly handles Windows case-folding; it does not canonicalize symlinks.Meanwhile, when the AppHost is started,
ProjectLocator.UseOrFindAppHostProjectFileAsyncdoes canonicalize before the socket is created (src/Aspire.Cli/Projects/ProjectLocator.cs:708):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
AppHostConnectionResolverbefore computingtargetPath. Either reuse the snippet above or factor it into a small helper called from both sites:This will also fix
aspire stop --apphost <symlinked>,aspire exec --apphost <symlinked>, and any other command that resolves a backchannel connection from an explicit--apphostargument.Workaround
Pass the canonical path explicitly, e.g.
aspire describe --apphost "$(realpath /tmp/aspire-D2/apphost.cs)", or omit--apphostandcdinto the AppHost directory.