microsoft/aspire · error · InvalidOperationException
Browser debug connection closed by the remote endpoint with…
Error message
Browser debug connection closed by the remote endpoint with status '{closeStatus}' ({(int)closeStatus}): {result.CloseStatusDescription} What it means
Thrown by BrowserLogsWebSocketCdpTransport.ReceiveAsync (BrowserLogsCdpConnection.cs:465) when the browser closes the CDP WebSocket instead of sending more messages. It is an InvalidOperationException raised because Aspire expects the browser debug connection to stay open for the lifetime of the tracked browser session, so a Close frame is unexpected and its status/description are surfaced as diagnostics for reconnect logic and resource logs.
Solutions
- Check the close status in the message: 1006/abnormal or empty description means the browser process died — inspect the browser's own logs/exit code first.
- Ensure the browser is not closed manually while the AppHost is tracking it; keep the browser window open during the session.
- Verify no proxy, VPN, or antivirus terminates idle WebSocket connections between the AppHost and the browser's debug port.
- If the browser exits immediately, confirm the debug port is not already in use by another browser instance (another instance will exit with 'DevToolsActivePort file doesn't exist' style errors).
- Dispose/recreate the BrowserLogsCdpConnection and relaunch the browser to re-establish the connection.
Example fix
// before
var connection = new BrowserLogsCdpConnection(...);
await connection.StreamLogsForeverAsync(ct);
// after
try
{
await connection.StreamLogsForeverAsync(ct);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("closed by the remote endpoint"))
{
logger.LogWarning(ex, "Browser CDP connection dropped; relaunching browser.");
connection = await RelaunchBrowserAsync();
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before relying on a long-lived CDP session, confirm the browser process is alive:
bool IsBrowserAlive(Process? browser) => browser is { HasExited: false }; Type guard
static bool IsUnexpectedClosure(InvalidOperationException ex) =>
ex.Message.StartsWith("Browser debug connection closed by the remote endpoint", StringComparison.Ordinal); Try / catch
try
{
await transport.ReceiveAsync(ct);
}
catch (InvalidOperationException ex) when (IsUnexpectedClosure(ex))
{
logger.LogWarning(ex, "CDP WebSocket closed by browser; scheduling relaunch.");
await RelaunchBrowserAndReconnectAsync();
} Prevention
- Do not close the browser window while the AppHost is tracking it.
- Monitor the browser process exit before/at the same time as CDP errors to distinguish crash vs close.
- Avoid proxies/VPNs that kill idle WebSockets between the AppHost and the debug port.
- Ensure the debug port is exclusive to this browser instance.
When it happens
Trigger: The remote browser sends a WebSocket Close frame during _webSocket.ReceiveAsync while the transport is reading CDP events — e.g. the browser process exits or is killed while logs are being streamed over the --remote-debugging-port WebSocket transport.
Common situations: The user closes the browser window; the browser crashes; the AppHost or OS kills the browser (timeout, shutdown); a proxy or firewall terminates the idle WebSocket; browser extensions force a restart of the browser process.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- Tracked browser host does not expose a WebSocket debug…
- (CDP error ).
- Timed out waiting for a tracked browser protocol response to
- Tracked browser protocol event method was not a string.
- Tracked browser protocol frame ended unexpectedly.
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/14a925efe894231d.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.Browsers/BrowserLogsCdpConnection.cs:465
private readonly TimeSpan _closeTimeout = closeTimeout;
private readonly WebSocket _webSocket = webSocket;
public async Task SendAsync(ReadOnlyMemory<byte> frame, CancellationToken cancellationToken)
{
await _webSocket.SendAsync(frame, WebSocketMessageType.Text, endOfMessage: true, cancellationToken).ConfigureAwait(false);
}
public async Task<byte[]> ReceiveAsync(CancellationToken cancellationToken)
{
var buffer = new byte[16 * 1024];
using var messageBuffer = new MemoryStream();
while (true)
{
var result = await _webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
if (result.MessageType == WebSocketMessageType.Close)
{
throw CreateUnexpectedConnectionClosureException(result);
}
// Large CDP events can span multiple websocket frames. Buffer until EndOfMessage so protocol parsing
// always sees one complete JSON message, matching the frames observed from a real browser.
messageBuffer.Write(buffer, 0, result.Count);
if (result.EndOfMessage)
{
return messageBuffer.ToArray();
}
}
}
public async ValueTask DisposeAsync()
{
try
{
if (_webSocket.State is WebSocketState.Open or WebSocketState.CloseReceived)
{View on GitHub (pinned to 25830f84bd)