{"record":{"id":"c0aea1fb38100ec3","repo":"microsoft/aspire","slug":"dashboard-exited-with-exit-code-0","errorCode":null,"errorMessage":"Dashboard exited with exit code {0}.","messagePattern":"Dashboard exited with exit code (.+?)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Profiling/ProfileCaptureService.cs","lineNumber":281,"sourceCode":"            _profileDataQuietPolls = profileDataQuietPolls;\n            _layoutLease = layoutLease;\n        }\n\n        public async Task WaitForDashboardAsync(TimeSpan timeout, TimeSpan pollInterval, CancellationToken cancellationToken)\n        {\n            // The private dashboard starts asynchronously and only becomes useful once Kestrel is\n            // accepting requests on its generated loopback URL. Poll the root endpoint instead of\n            // waiting on process start alone, and watch the exit task so startup failures surface as\n            // dashboard errors instead of generic connection timeouts.\n            using var timeoutCts = new CancellationTokenSource(timeout, _timeProvider);\n            using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);\n\n            while (true)\n            {\n                if (_dashboardExitTask.IsCompleted)\n                {\n                    var exitCode = await GetDashboardExitCodeAsync().ConfigureAwait(false);\n                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, DashboardCommandStrings.DashboardExitedWithError, exitCode));\n                }\n\n                try\n                {\n                    using var client = _httpClientFactory.CreateClient();\n                    // ProfileCaptureOptions allocates a loopback ephemeral port before the private\n                    // dashboard starts. StartAsync passes that exact URL to Kestrel via\n                    // --urls/ASPNETCORE_URLS, so probing _options.DashboardUrl verifies the collector\n                    // bound to the random port reserved for this capture session.\n                    using var response = await client.GetAsync(_options.DashboardUrl, linkedCts.Token).ConfigureAwait(false);\n                    if (response.IsSuccessStatusCode)\n                    {\n                        return;\n                    }\n                }\n                catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)\n                {\n                    throw new TimeoutException(DashboardCommandStrings.DashboardStartTimedOut);","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Profiling/ProfileCaptureService.cs#L263-L299","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: port already in use\n--aspire_otel_grpc_endpoint_url=http://127.0.0.1:18889 // occupied\n// after: let the port be chosen or free the conflicting port\naspire ... (stop the other process bound to 18889 first)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try\n{\n    var session = await captureService.StartAsync(options, ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"Dashboard exited\"))\n{\n    logger.LogError(ex, \"Dashboard crashed during startup; inspect its stdout/stderr capture for the cause.\");\n}","preventionTips":["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)."],"tags":["process-exit","dashboard","cli","profiling","port-conflict"],"backgroundTag":"process-exited-unexpectedly","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}