microsoft/aspire · error · ArgumentException

CLI path must be provided

Error message

CLI path must be provided

What it means

The DevTunnelCli constructor validates the path to the devtunnel CLI executable before storing it. If the constructor receives an empty, null, or whitespace-only path, it refuses to construct and throws this ArgumentException so that downstream process launches do not fail obscurely.

Solutions

  1. Set the ASPIRE_DEVTUNNEL_CLI_PATH environment variable to the full path of the devtunnel executable
  2. Use DevTunnelCli.GetCliPath(configuration) to obtain a path (it falls back to 'devtunnel') instead of constructing with a raw config value
  3. Verify the devtunnel CLI is installed and on PATH so a valid fallback path exists
  4. Guard the constructor input: pass configuration["ASPIRE_DEVTUNNEL_CLI_PATH"] ?? "devtunnel"

Example fix

// before
var cli = new DevTunnelCli(configuration["ASPIRE_DEVTUNNEL_CLI_PATH"]); // null if unset
// after
var cli = new DevTunnelCli(DevTunnelCli.GetCliPath(configuration)); // falls back to "devtunnel"
Defensive patterns

Strategy: validation

Validate before calling

var path = configuration["ASPIRE_DEVTUNNEL_CLI_PATH"];
if (string.IsNullOrWhiteSpace(path))
    path = "devtunnel"; // or fail fast with a clear message before constructing DevTunnelCli

Try / catch

try
{
    var cli = new DevTunnelCli(path);
}
catch (ArgumentException ex)
{
    logger.LogError(ex, "devtunnel CLI path is not configured; set ASPIRE_DEVTUNNEL_CLI_PATH.");
    return;
}

Prevention

When it happens

Trigger: Calling new DevTunnelCli(null), new DevTunnelCli(""), or new DevTunnelCli(" "), or wiring the value of ASPIRE_DEVTUNNEL_CLI_PATH (or the fallback from GetCliPath) into the constructor when it is null/whitespace.

Common situations: Configuration returns null because the ASPIRE_DEVTUNNEL_CLI_PATH environment variable is unset and a caller bypasses GetCliPath; a config string is trimmed to empty; a DI-registered factory resolves an unset option property.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting.DevTunnels/DevTunnelCli.cs:29

namespace Aspire.Hosting.DevTunnels;

internal class DevTunnelCli
{
    public const int ResourceConflictsWithExistingExitCode = 1;
    public const int ResourceNotFoundExitCode = 2;

    public static readonly Version MinimumSupportedVersion = new(1, 0, 1435);

    private readonly string _cliPath;

    public static string GetCliPath(IConfiguration configuration) => configuration["ASPIRE_DEVTUNNEL_CLI_PATH"] ?? "devtunnel";

    public DevTunnelCli(string filePath)
    {
        if (string.IsNullOrWhiteSpace(filePath))
        {
            throw new ArgumentException("CLI path must be provided", nameof(filePath));
        }

        _cliPath = filePath;
    }

    public Task<int> GetVersionAsync(TextWriter? outputWriter = null, TextWriter? errorWriter = null, ILogger? logger = default, CancellationToken cancellationToken = default)
        => RunAsync(["--version", "--nologo"], outputWriter, errorWriter, logger, cancellationToken);

    public Task<int> UserLoginMicrosoftAsync(ILogger? logger = default, CancellationToken cancellationToken = default)
        => RunAsync(["user", "login", "--entra", "--json", "--nologo"], null, null, useShellExecute: true, logger, cancellationToken);

    public Task<int> UserLoginGitHubAsync(ILogger? logger = default, CancellationToken cancellationToken = default)
        => RunAsync(["user", "login", "--github", "--json", "--nologo"], null, null, useShellExecute: true, logger, cancellationToken);

    public Task<int> UserLogoutAsync(TextWriter? outputWriter = null, TextWriter? errorWriter = null, ILogger? logger = default, CancellationToken cancellationToken = default)
        => RunAsync(new ArgsBuilder(["user", "logout", "--json", "--nologo"])
        , outputWriter, errorWriter, logger, cancellationToken);

View on GitHub (pinned to 25830f84bd)