microsoft/aspire · error · DistributedApplicationException
Failed to parse JSON output into type
Error message
Failed to parse JSON output into type '{typeName}':
{output} What it means
CallCliAsJsonAsync captures the devtunnel CLI's stdout and deserializes it into type T. If the output is not valid JSON (JsonException), the client logs the raw output and rethrows it wrapped in this DistributedApplicationException.
Solutions
- Update the devtunnel CLI to the latest version so JSON output matches what Aspire expects
- Read the raw output in the message to see exactly what the CLI returned
- Verify the command succeeds when run manually (`devtunnel list --json`) and compare output
- Check whether another tool/alias named `devtunnel` is shadowing the real CLI on PATH
Example fix
// before // devtunnel CLI v0.1 prints text banners -> JsonException // after // update CLI devtunnel update # or reinstall latest from https://learn.microsoft.com/azure/developer/dev-tunnels // then retry the app host
Defensive patterns
Strategy: validation
Validate before calling
var psi = new ProcessStartInfo("devtunnel", "--version") { RedirectStandardOutput = true };
using var p = Process.Start(psi)!;
var version = (await p.StandardOutput.ReadToEndAsync()).Trim();
if (Version.TryParse(version.Split(' ')[^1].TrimStart('v'), out var v) && v < new Version(1, 0))
throw new InvalidOperationException($"devtunnel CLI too old: {version}"); Type guard
static bool LooksLikeJson(string s) => s.TrimStart().StartsWith('{') || s.TrimStart().StartsWith('['); Try / catch
try { await client.ListTunnelsAsync(logger, ct); }
catch (DistributedApplicationException ex) when (ex.Message.StartsWith("Failed to parse JSON"))
{ logger.LogError(ex, "devtunnel CLI returned non-JSON output; update the CLI"); throw; } Prevention
- Keep the devtunnel CLI up to date
- Run the failing command manually with --json to inspect raw output
- Ensure no alias shadows devtunnel on PATH
When it happens
Trigger: The devtunnel CLI prints human-readable text, warnings, or login prompts to stdout instead of JSON; an old CLI version emits a different output shape; the command fails before producing JSON.
Common situations: Outdated devtunnel CLI without JSON output support for a flag; CLI emitting deprecation/telemetry banners on stdout; truncation or interleaved stderr/stdout in redirected streams.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- CLI path must be provided
- 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
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/0bd49ca379fe9e5d.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.DevTunnels/DevTunnelCliClient.cs:339
logger?.LogError("CLI call returned empty output with exit code '{ExitCode}'.", exitCode);
return (default, exitCode, "CLI call returned empty output.");
}
try
{
if (!string.IsNullOrEmpty(propertyName))
{
output = JsonDocument.Parse(output).RootElement.GetProperty(propertyName).GetRawText();
logger?.LogTrace("Extracted JSON property '{PropertyName}':\n{Output}", propertyName, output);
}
var result = JsonSerializer.Deserialize<T>(output, _jsonOptions);
logger?.LogTrace("JSON output successfully deserialized to '{TypeName}' instance", typeof(T).Name);
return (result, 0, default);
}
catch (JsonException ex)
{
logger?.LogError(ex, "Failed to parse JSON output into type '{TypeName}':\n{Output}", typeof(T).Name, output);
throw new DistributedApplicationException($"Failed to parse JSON output into type '{typeof(T).Name}':\n{output}", ex);
}
}
private record DevTunnelDeleteResult(string DeletedTunnel);
}
View on GitHub (pinned to 25830f84bd)