microsoft/aspire · error · InvalidOperationException
Dashboard exited with exit code
Error message
Dashboard exited with exit code {0}. What it means
While waiting for the profiling dashboard to become ready, ProfileCaptureSession.WaitForDashboardAsync polls its health endpoint. If the dashboard process exits before it ever responds (the _dashboardExitTask completes first), the loop throws this error with the process exit code instead of continuing to poll a dead process. Exit code 0 still triggers the error here because a ready dashboard should never exit during startup.
Solutions
- Check the reported exit code and the dashboard's captured stdout/stderr output for the crash reason.
- Free or change the DashboardUrl/Otlp ports if a conflict is indicated.
- Re-extract or reinstall the CLI bundle ('aspire setup --force') in case the binary is corrupted or mismatched.
- Retry the profiling capture; a transient environment issue may have caused the exit.
Example fix
// before: port already in use --aspire_otel_grpc_endpoint_url=http://127.0.0.1:18889 // occupied // after: let the port be chosen or free the conflicting port aspire ... (stop the other process bound to 18889 first)
Defensive patterns
Strategy: try-catch
Try / catch
try
{
var session = await captureService.StartAsync(options, ct);
}
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Dashboard exited"))
{
logger.LogError(ex, "Dashboard crashed during startup; inspect its stdout/stderr capture for the cause.");
} Prevention
- Ensure the dashboard and OTLP ports are free before starting a capture.
- Do not kill child aspire-managed processes while profiling.
- Keep the CLI bundle binary versions consistent (no mixed installs).
When it happens
Trigger: Calling ProfileCaptureService.StartAsync where the spawned aspire-managed dashboard terminates during the readiness-wait window: crash on startup (bad config, port conflict, missing dependency), or being killed externally, before the health poll succeeds.
Common situations: Another process already bound to the dashboard's configured port; the dashboard crashes due to invalid OTLP endpoint configuration; the binary is an incompatible/older version; system resource limits (out of memory) kill the process; user or a script terminated the process.
Related errors
- Dashboard did not become ready within the expected time.
- Failed to start Aspire dashboard
- The CLI bundle layout was found, but the dashboard binary…
- Foundry CLI command ' ' exited before producing required…
- Already connected to AppHost backchannel.
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/c0aea1fb38100ec3.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Cli/Profiling/ProfileCaptureService.cs:281
_profileDataQuietPolls = profileDataQuietPolls;
_layoutLease = layoutLease;
}
public async Task WaitForDashboardAsync(TimeSpan timeout, TimeSpan pollInterval, CancellationToken cancellationToken)
{
// The private dashboard starts asynchronously and only becomes useful once Kestrel is
// accepting requests on its generated loopback URL. Poll the root endpoint instead of
// waiting on process start alone, and watch the exit task so startup failures surface as
// dashboard errors instead of generic connection timeouts.
using var timeoutCts = new CancellationTokenSource(timeout, _timeProvider);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
while (true)
{
if (_dashboardExitTask.IsCompleted)
{
var exitCode = await GetDashboardExitCodeAsync().ConfigureAwait(false);
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, DashboardCommandStrings.DashboardExitedWithError, exitCode));
}
try
{
using var client = _httpClientFactory.CreateClient();
// ProfileCaptureOptions allocates a loopback ephemeral port before the private
// dashboard starts. StartAsync passes that exact URL to Kestrel via
// --urls/ASPNETCORE_URLS, so probing _options.DashboardUrl verifies the collector
// bound to the random port reserved for this capture session.
using var response = await client.GetAsync(_options.DashboardUrl, linkedCts.Token).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return;
}
}
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
throw new TimeoutException(DashboardCommandStrings.DashboardStartTimedOut);View on GitHub (pinned to 25830f84bd)