microsoft/aspire · error · RuntimeException

Disconnected from AppHost

Error message

Disconnected from AppHost

What it means

The Aspire Java code-generation transport (JSON-RPC over stdio to the AppHost) throws when a request is attempted after the connection has already been marked disconnected. sendRequest checks the disconnected flag under connectionStateLock and throws the disconnected exception ('Disconnected from AppHost') rather than queueing a request that could never be answered.

Solutions

  1. Check the AppHost process/logs to see why it exited (crash, dashboard shutdown, unhandled exception) and restart it.
  2. Recreate or reconnect the transport/session after a disconnect instead of reusing the stale client — the disconnected flag is terminal for that instance.
  3. Inspect the earlier 'Failed to send request ...' or reader-loop exception that triggered handleDisconnect to find the root cause (broken pipe, EOF on stdin/stdout).
  4. Guard long-lived clients with connection-state checks or retry logic that rebuilds the transport on 'Disconnected from AppHost' errors.

Example fix

// before: reusing a stale transport after disconnect
Object result = transport.invokeCapability("build", args);
// after: recreate the transport when disconnected
try {
    Object result = transport.invokeCapability("build", args);
} catch (RuntimeException e) {
    if (String.valueOf(e.getMessage()).contains("Disconnected from AppHost")) {
        transport = Transport.connect(appHostProcess);
        Object result = transport.invokeCapability("build", args);
    } else { throw e; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before each request batch, check transport liveness if exposed.
if (transport.isDisconnected()) { transport = Transport.connect(appHostProcess); }

Try / catch

try {
    Object result = transport.invokeCapability(capabilityId, args);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("Disconnected from AppHost")) {
        // restart AppHost and rebuild the transport, then retry once
        appHost = AppHost.start();
        transport = Transport.connect(appHost.getProcess());
        Object result = transport.invokeCapability(capabilityId, args);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: invokeCapability or any sendRequest call is made after a previous I/O failure or EOF triggered handleDisconnect() — e.g. the AppHost process exited, the stdio pipe broke, or a prior send threw IOException and marked the transport disconnected.

Common situations: AppHost crashed or was stopped while the generated Java client was still issuing capability invocations; long-running session outliving the AppHost; an earlier request failed with IOException and subsequent calls now hit the disconnected guard; not restarting/reconnecting the transport after a disconnect.

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.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/086222479aad0ee9. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.CodeGeneration.Java/Resources/Transport.java:416

                }
            });
        } finally {
            for (String cancellationId : new HashSet<>(cancellationIds)) {
                unregisterCancellation(cancellationId);
            }
        }
    }

    private Object sendRequest(String method, Object params) {
        return sendRequest(method, params, null);
    }

    private Object sendRequest(String method, Object params, Runnable requestSent) {
        CompletableFuture<Object> pendingResponse = new CompletableFuture<>();
        int id;
        synchronized (connectionStateLock) {
            if (disconnected) {
                throw disconnectedException();
            }

            id = requestId.incrementAndGet();
            pendingRequests.put(id, pendingResponse);
        }

        Map<String, Object> request = new HashMap<>();
        request.put("jsonrpc", "2.0");
        request.put("id", id);
        request.put("method", method);
        request.put("params", params);

        debug("Sending request " + method + " with id=" + id);

        try {
            ensureReaderLoopStarted();
            sendMessage(request);
            if (requestSent != null) {

View on GitHub (pinned to 25830f84bd)