Description
The DevTunnel resource created via Aspire.Hosting.DevTunnels is always reported as Unhealthy in the Aspire dashboard, even though the tunnel is actually running correctly and the public URL is reachable and forwards traffic to the referenced resource as expected.
This reproduces both when:
- an explicit
tunnelId is passed to AddDevTunnel(name, tunnelId: "..."), and
- no
tunnelId is passed and Aspire auto-generates one (e.g. shibboleth-tunnel-af080a54), on a tunnel that never previously existed.
So the issue is not related to reusing/naming a pre-existing tunnel.
Root cause (found via manual repro)
DevTunnelHealthCheck calls DevTunnelCliClient.GetAccessAsync(tunnelId, portNumber, ...), which shells out to:
devtunnel access list <tunnelId> -p <portNumber> --json
using the short/bare tunnel id (the one supplied to/generated by AddDevTunnel, without the cluster suffix).
This command call fails:
devtunnel access list shibboleth-tunnel-af080a54 -p 57877 --json
Tunnel service error: Not Found The requested tunnel was not found.
However, the very same bare id works fine for other CLI operations against the same tunnel:
devtunnel show shibboleth-tunnel-af080a54 --json # OK
devtunnel port show shibboleth-tunnel-af080a54 -p 57877 --json # OK
devtunnel access list shibboleth-tunnel-af080a54 --json # OK (no -p)
And it succeeds if the fully cluster-qualified id is used instead:
devtunnel access list shibboleth-tunnel-af080a54.eun1 -p 57877 --json # OK
So the combination of a bare tunnel id + -p <port> for devtunnel access list appears to fail to resolve the tunnel on the service side, while the same bare id works for every other subcommand (including access list without -p). Because DevTunnelHealthCheck/DevTunnelCliClient.GetAccessAsync always passes the bare id together with the port number, the health check fails on every run, permanently marking the DevTunnel resource (and downstream DevTunnelPort resource) as Unhealthy/Starting, even though the tunnel is fully functional.
Reproduction steps
- Create an AppHost with:
var builder = DistributedApplication.CreateBuilder(args);
var app = builder.AddContainer("app", "some/image", "latest")
.WithHttpsEndpoint(targetPort: 443, name: "app-https");
var tunnel = builder.AddDevTunnel("app-tunnel")
.WithAnonymousAccess()
.WithReference(app);
builder.Build().Run();
aspire start / aspire run.
- Wait for the container to become Healthy and the tunnel to finish hosting (
Dev tunnel '...' created / Ready to accept connections... in the tunnel resource logs).
- Observe in
aspire describe (or the dashboard) that the DevTunnel resource stays Unhealthy, with a health report similar to:
Failed to check dev tunnel '<id>': Failed to get access details for '<id>' port <port>.
Exit code 1: Tunnel service error: Not Found The requested tunnel was not found.
- Confirm the tunnel is actually working:
curl the public https://<code>-<port>.<cluster>.devtunnels.ms URL — it correctly proxies to the local resource.
- Manually reproduce the failing health-check call:
devtunnel access list <bare-id> -p <port> --json
Note it returns Tunnel service error: Not Found, while devtunnel access list <bare-id>.<cluster> -p <port> --json succeeds.
Expected behavior
The DevTunnel resource should report Healthy once the tunnel is created and hosting connections are established, matching the tunnel's real, working state.
Actual behavior
The DevTunnel resource is permanently Unhealthy, and the associated DevTunnelPort resource never leaves the Starting state, because the health check's devtunnel access list <id> -p <port> CLI call fails to resolve the tunnel when given a non-cluster-qualified id.
Suggested fix
DevTunnelCliClient/DevTunnelHealthCheck could:
- Use the cluster-qualified tunnel id (as returned by
devtunnel create/devtunnel update, e.g. <id>.<cluster>) for subsequent CLI calls that combine an id with -p <port>, instead of the original bare id, or
- Avoid passing
-p to access list when only checking overall tunnel/port reachability, or
- Fall back to
devtunnel port show <id> -p <port> (confirmed working) instead of access list ... -p <port> for the health check.
Versions used
- Aspire.AppHost.Sdk / Aspire.Hosting.DevTunnels:
13.4.6
- Aspire CLI:
13.4.6+87fe259e4fc244c599019a7b1304c85a1488f248
- devtunnel CLI:
1.0.1972+07cc55c789
- Tunnel service cluster involved:
eun1 (devtunnel CLI reports connecting to euw service endpoint, tunnels get provisioned in eun1)
- OS: Windows
- TargetFramework: net10.0
Description
The
DevTunnelresource created viaAspire.Hosting.DevTunnelsis always reported as Unhealthy in the Aspire dashboard, even though the tunnel is actually running correctly and the public URL is reachable and forwards traffic to the referenced resource as expected.This reproduces both when:
tunnelIdis passed toAddDevTunnel(name, tunnelId: "..."), andtunnelIdis passed and Aspire auto-generates one (e.g.shibboleth-tunnel-af080a54), on a tunnel that never previously existed.So the issue is not related to reusing/naming a pre-existing tunnel.
Root cause (found via manual repro)
DevTunnelHealthCheckcallsDevTunnelCliClient.GetAccessAsync(tunnelId, portNumber, ...), which shells out to:using the short/bare tunnel id (the one supplied to/generated by
AddDevTunnel, without the cluster suffix).This command call fails:
However, the very same bare id works fine for other CLI operations against the same tunnel:
And it succeeds if the fully cluster-qualified id is used instead:
So the combination of a bare tunnel id +
-p <port>fordevtunnel access listappears to fail to resolve the tunnel on the service side, while the same bare id works for every other subcommand (includingaccess listwithout-p). BecauseDevTunnelHealthCheck/DevTunnelCliClient.GetAccessAsyncalways passes the bare id together with the port number, the health check fails on every run, permanently marking theDevTunnelresource (and downstreamDevTunnelPortresource) as Unhealthy/Starting, even though the tunnel is fully functional.Reproduction steps
aspire start/aspire run.Dev tunnel '...' created/Ready to accept connections...in the tunnel resource logs).aspire describe(or the dashboard) that theDevTunnelresource staysUnhealthy, with a health report similar to:curlthe publichttps://<code>-<port>.<cluster>.devtunnels.msURL — it correctly proxies to the local resource.Tunnel service error: Not Found, whiledevtunnel access list <bare-id>.<cluster> -p <port> --jsonsucceeds.Expected behavior
The
DevTunnelresource should reportHealthyonce the tunnel is created and hosting connections are established, matching the tunnel's real, working state.Actual behavior
The
DevTunnelresource is permanentlyUnhealthy, and the associatedDevTunnelPortresource never leaves theStartingstate, because the health check'sdevtunnel access list <id> -p <port>CLI call fails to resolve the tunnel when given a non-cluster-qualified id.Suggested fix
DevTunnelCliClient/DevTunnelHealthCheckcould:devtunnel create/devtunnel update, e.g.<id>.<cluster>) for subsequent CLI calls that combine an id with-p <port>, instead of the original bare id, or-ptoaccess listwhen only checking overall tunnel/port reachability, ordevtunnel port show <id> -p <port>(confirmed working) instead ofaccess list ... -p <port>for the health check.Versions used
13.4.613.4.6+87fe259e4fc244c599019a7b1304c85a1488f2481.0.1972+07cc55c789eun1(devtunnel CLI reports connecting toeuwservice endpoint, tunnels get provisioned ineun1)