microsoft/aspire · critical · DistributedApplicationException

{result.ValidationMessage}

Error message

{result.ValidationMessage}

What it means

AddDevTunnel validates the dev tunnel resource (including the local Dev Tunnels CLI version, via ValidateDevTunnelCliVersionAsync) before proceeding to login and tunnel creation. If validation fails, result.ValidationMessage describes the concrete problem and the builder throws a DistributedApplicationException to abort startup rather than creating a broken tunnel. It is a fail-fast guard so misconfigured tunnels or an unusable CLI are surfaced immediately.

Solutions

  1. Read the thrown ValidationMessage to see the exact validation failure it reports.
  2. Install or update the Dev Tunnels CLI per https://learn.microsoft.com/azure/developer/dev-tunnels/get-started#install and ensure 'devtunnel' resolves on PATH.
  3. Re-run the app host after the CLI fix; validation is re-executed on each start.

Example fix

// before (CLI missing/outdated => validation fails at startup)
var tunnel = builder.AddDevTunnel("my-tunnel").WithReference(api);
// after: install/update devtunnel first, e.g.
// winget install Microsoft.DevTunnels --version <latest>
// then verify: devtunnel --version
var tunnel = builder.AddDevTunnel("my-tunnel").WithReference(api);
Defensive patterns

Strategy: validation

Validate before calling

// before starting the app host
var psi = new System.Diagnostics.ProcessStartInfo("devtunnel", "--version") { RedirectStandardOutput = true };
using var p = System.Diagnostics.Process.Start(psi)!;
var ver = p.StandardOutput.ReadToEnd();
if (string.IsNullOrWhiteSpace(ver)) throw new InvalidOperationException("Dev Tunnels CLI not installed/on PATH");

Prevention

When it happens

Trigger: Calling AddDevTunnel/AddDevTunnelForPolyglot when commandValidator.ValidateAsync returns IsValid=false for the tunnel resource — typically because the Dev Tunnels CLI is not installed, is missing from PATH, or has an unsupported version detected by the CLI version validation callback.

Common situations: Fresh machines or CI agents where the Dev Tunnels CLI (devtunnel) was never installed; an outdated devtunnel version below the minimum supported by the package; CLI installed only for another user/shell so it is not on PATH.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/514af094fd7a3a94. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs:138

            {
                var logger = e.Services.GetRequiredService<ResourceLoggerService>().GetLogger(tunnelResource);
                var eventing = e.Services.GetRequiredService<IDistributedApplicationEventing>();
                var commandValidator = e.Services.GetRequiredService<IRequiredCommandValidator>();
                var devTunnelEnvironmentManager = e.Services.GetRequiredService<DevTunnelLoginManager>();
                var devTunnelClient = e.Services.GetRequiredService<IDevTunnelClient>();

                // Validate the CLI is available and version is supported.
                // We use manual validation here instead of WithRequiredCommand call because our
                // OnBeforeResourceStarted handler runs before the global RequiredCommandValidationLifecycleHook runs.
                var cliAnnotation = new RequiredCommandAnnotation(tunnelResource.Command)
                {
                    HelpLink = "https://learn.microsoft.com/azure/developer/dev-tunnels/get-started#install",
                    ValidationCallback = ValidateDevTunnelCliVersionAsync
                };
                var result = await commandValidator.ValidateAsync(tunnelResource, cliAnnotation, ct).ConfigureAwait(false);
                if (!result.IsValid)
                {
                    throw new DistributedApplicationException(result.ValidationMessage);
                }

                // Login to the dev tunnels service if needed
                logger.LogInformation("Ensuring user is logged in to dev tunnel service");
                await devTunnelEnvironmentManager.EnsureUserLoggedInAsync(ct).ConfigureAwait(false);

                // Create the dev tunnel
                string resolvedTunnelId;
                try
                {
                    logger.LogInformation("Creating dev tunnel '{TunnelId}'", tunnelResource.TunnelId);
                    var tunnelStatus = await devTunnelClient.CreateTunnelAsync(tunnelResource.TunnelId, tunnelResource.Options, logger, ct).ConfigureAwait(false);
                    // The CLI resolves a bare ID and returns its cluster-qualified ID. Use that ID for
                    // port operations because bare IDs may not resolve tunnels across clusters.
                    // See https://github.com/microsoft/aspire/issues/18790.
                    resolvedTunnelId = tunnelStatus.TunnelId;
                    logger.LogDebug("Dev tunnel '{TunnelId}' created", tunnelResource.TunnelId);
                }

View on GitHub (pinned to 25830f84bd)