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
- Set the ASPIRE_DEVTUNNEL_CLI_PATH environment variable to the full path of the devtunnel executable
- Use DevTunnelCli.GetCliPath(configuration) to obtain a path (it falls back to 'devtunnel') instead of constructing with a raw config value
- Verify the devtunnel CLI is installed and on PATH so a valid fallback path exists
- 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
- Always resolve the CLI path via DevTunnelCli.GetCliPath(configuration)
- Set ASPIRE_DEVTUNNEL_CLI_PATH in CI/dev environments where devtunnel is not on PATH
- Assert the path is non-empty in startup configuration validation
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
- Failed to delete port
- Failed to get access details for
- Failed to get devtunnel CLI version. Output
- Failed to get user login status. Exit code
- Failed to parse JSON output into type
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)