microsoft/aspire · critical · InvalidOperationException
AppHost server process exited before the RPC connection…
Error message
AppHost server process exited before the RPC connection could be established. Exit code: {code}. What it means
Thrown by AppHostServerSession.GetRpcClientAsync when the AppHost server process exits before the CLI's RPC connection attempt succeeds. The session races serverExitTask against connectTask; if the exit task wins, it cancels the connection and throws with the server's exit code when available, so the root cause (server crash) is surfaced instead of an opaque connect timeout.
Solutions
- Inspect the server process stdout/stderr captured by the session for the startup error
- Fix the AppHost build/runtime error causing the exit (missing SDK, compile error)
- Verify the server launch arguments and environment (DOTNET_ROOT etc.) are correct
- Check the exit code reported in the message and correlate with server logs
Example fix
// before var session = await AppHostServerSession.CreateAsync(badProjectPath); // exits code 1 // after dotnet build MyApp.AppHost.csproj // fix compile errors first var session = await AppHostServerSession.CreateAsync(projectPath);
Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm the AppHost builds before launching the server session
var buildResult = await Process.RunAsync("dotnet", $"build {appHostProjectPath}");
if (buildResult.ExitCode != 0)
throw new InvalidOperationException("AppHost build failed; fix before starting a session."); Try / catch
try
{
var rpc = await session.GetRpcClientAsync(ct);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("exited before"))
{
var log = session.CapturedServerOutput;
logger.LogError(ex, "Server exited early. Output: {Output}", log);
} Prevention
- Capture and inspect the server process's stdout/stderr on every launch
- Fix AppHost build errors before starting sessions
- Verify DOTNET_ROOT and required SDKs are available to the child process
- Record the exit code from the message to guide troubleshooting
When it happens
Trigger: Calling GetRpcClientAsync when the spawned server process terminates (crash, bad args, port/env problems) while ConnectToServerAsync is still retrying.
Common situations: AppHost project fails to build at startup, missing SDK or runtime on the machine, invalid command-line arguments passed to the server, unhandled exception in server startup, or the process killed by the OS.
Related errors
- -32000
- Already connected to AppHost backchannel.
- An input name is required when uploading a file.
- An interaction ID is required when uploading a file.
- callback ID missing
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/d94f8a10eab349d4.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Cli/Projects/AppHostServerSession.cs:331
var connectTask = AppHostRpcClient.ConnectAsync(socketPath, _authenticationToken, _environment, _profilingTelemetry, connectCts.Token);
var completedTask = await Task.WhenAny(connectTask, serverExitTask).ConfigureAwait(false);
if (completedTask == connectTask)
{
// Stop the process-exit watcher once the RPC connection wins the race.
connectCts.Cancel();
ObserveFaultedTask(serverExitTask);
_rpcClient = await connectTask.ConfigureAwait(false);
return _rpcClient;
}
await serverExitTask.ConfigureAwait(false);
// Stop the retrying connection attempt once the server has exited, then observe any
// cancellation/failure it reports so the losing task cannot raise an unobserved exception.
connectCts.Cancel();
ObserveFaultedTask(connectTask);
var exitCode = TryGetServerExitCode();
throw new InvalidOperationException(
exitCode is { } code
? $"AppHost server process exited before the RPC connection could be established. Exit code: {code}."
: "AppHost server process exited before the RPC connection could be established.");
}
public async ValueTask DisposeAsync()
{
// Acquire _startGate the same way StartAsync does so dispose is serialized against an
// in-flight start: we either flip _disposed before StartAsync acquires the gate (it then
// throws ObjectDisposedException and spawns nothing) or after it has fully published the
// execution + run task (we then tear that down below). The gate is released immediately;
// the teardown runs outside it so a late StartAsync observes _disposed and bails fast.
bool alreadyDisposed;
await _startGate.WaitAsync().ConfigureAwait(false);
try
{
alreadyDisposed = _disposed;
_disposed = true;View on GitHub (pinned to 25830f84bd)