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

  1. Check the reported exit code and the dashboard's captured stdout/stderr output for the crash reason.
  2. Free or change the DashboardUrl/Otlp ports if a conflict is indicated.
  3. Re-extract or reinstall the CLI bundle ('aspire setup --force') in case the binary is corrupted or mismatched.
  4. 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

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


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)