microsoft/aspire · error · DistributedApplicationException

Failed to get devtunnel CLI version. Output

Error message

Failed to get devtunnel CLI version. Output: '{output}'. Error: '{error}'

What it means

DevTunnelCliClient.GetVersionAsync parses the output of 'devtunnel --version'. If the parsed output does not yield a version and the CLI produced any output or error text, the client throws this DistributedApplicationException including the raw output and error so the underlying CLI failure is visible.

Solutions

  1. Run 'devtunnel --version' manually and compare its output with what the client expects; upgrade or reinstall the CLI to a supported version
  2. Run 'devtunnel user login' if the error text indicates authentication failure
  3. Remove PATH shims/wrappers that add extra text to the CLI output
  4. Check the message's Output/Error fields for the exact CLI failure before retrying

Example fix

// before
var version = await client.GetVersionAsync(); // throws when CLI output unparseable
// after
var (stdout, stderr) = await CaptureAsync(() => new DevTunnelCli(path).GetVersionAsync());
logger.LogInformation("devtunnel version output: {Out} / {Err}", stdout, stderr); // diagnose first
var version = await client.GetVersionAsync();
Defensive patterns

Strategy: try-catch

Validate before calling

var probe = new StringBuilder();
var errs = new StringBuilder();
await cli.GetVersionAsync(probe, errs);
// if probe output is empty or unparseable, fix the CLI before resource startup depends on it

Try / catch

try
{
    var version = await client.GetVersionAsync();
}
catch (DistributedApplicationException ex)
{
    logger.LogError(ex, "devtunnel CLI version probe failed; check CLI install, login state, and version format.");
    throw; // fail fast: later tunnel operations will fail too
}

Prevention

When it happens

Trigger: Calling GetVersionAsync (directly or via DevTunnels resource startup) when the devtunnel CLI fails to produce a parseable version string — non-zero exit, empty output with error text, or unexpected output format from a changed CLI version.

Common situations: devtunnel CLI not logged in and prints an auth error; an old or preview devtunnel version whose --version output format differs; shell wrapper or PATH shim injecting extra output; CLI missing so errorWriter holds 'command not found'.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting.DevTunnels/DevTunnelCliClient.cs:63

                .FirstOrDefault(l => l.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
            var versionString = versionLine?.Length > prefix.Length
                ? versionLine[prefix.Length..].Trim()
                : output;

            // Trim the commit SHA suffix if present
            if (versionString.IndexOf('+') is >= 0 and var plusIndex)
            {
                versionString = versionString[..plusIndex];
            }

            if (Version.TryParse(versionString, out var version))
            {
                return version;
            }
        }

        var error = errorWriter.ToString().Trim();
        throw new DistributedApplicationException($"Failed to get devtunnel CLI version. Output: '{output}'. Error: '{error}'");
    }

    public async Task<DevTunnelStatus> CreateTunnelAsync(string tunnelId, DevTunnelOptions options, ILogger? logger = default, CancellationToken cancellationToken = default)
    {
        var attempts = 0;
        var exitCode = 0;
        string? error = null;
        string resolvedTunnelId = options.Region is not null ? $"{tunnelId}.{options.RegionCode}" : tunnelId;

        while (attempts < _maxCliAttempts)
        {
            logger?.LogTrace("Creating dev tunnel '{TunnelId}' with options: {Options}", tunnelId, options.ToLoggerString());
            if (attempts++ > 1)
            {
                logger?.LogTrace("Attempt {Attempt} of {MaxAttempts} to create dev tunnel '{TunnelId}'", attempts, _maxCliAttempts, tunnelId);
            }
            (var tunnel, exitCode, error) = await CallCliAsJsonAsync<DevTunnelStatus>((stdout, stderr, log, ct) => _cli.CreateTunnelAsync(tunnelId, options, stdout, stderr, log, ct),
                "tunnel",

View on GitHub (pinned to 25830f84bd)