microsoft/aspire · error
connection closed
Error message
connection closed
What it means
When the Go transport's Close runs without a prior error, it synthesizes "connection closed" as the error accompanying the close, then sends an error response over the wire. Callers of close (writeLoop, readLoop, or an outer close) surface this whenever the connection is torn down while work is pending, optionally joined with the underlying rawConn.Close() error.
Solutions
- Treat "connection closed" as retryable: reconnect (re-create the client from the socket path) and re-issue in-flight requests.
- Check REMOTE_APP_HOST_SOCKET_PATH reachability and confirm the AppHost process is still running.
- Inspect errors.Join output for an underlying closeErr (e.g. use of closed connection) to find the root cause.
- Avoid holding long-lived connections across AppHost restarts; re-read env connection info on reconnect.
Example fix
// before
resp, err := client.Invoke(ctx, cap, args)
if err != nil {
return err
}
// after
resp, err := client.Invoke(ctx, cap, args)
if err != nil {
if strings.Contains(err.Error(), "connection closed") {
client, err = reconnect() // re-dial from socket path
if err == nil {
resp, err = client.Invoke(ctx, cap, args)
}
}
if err != nil {
return err
}
} Defensive patterns
Strategy: retry
Validate before calling
if _, err := os.Stat(socketPath); err != nil {
return fmt.Errorf("apphost socket %s unreachable: %w", socketPath, err)
} Type guard
func isConnectionClosed(err error) bool {
return err != nil && strings.Contains(err.Error(), "connection closed")
} Try / catch
if err != nil {
if isConnectionClosed(err) {
if reerr := reconnect(); reerr == nil {
return invokeWithRetry(ctx, cap, args) // bounded retries
}
}
return err
} Prevention
- Wrap RPC calls in bounded reconnect-and-retry logic.
- Monitor AppHost process health; restarts manifest as this error.
- Re-read connection env vars on reconnect instead of caching them.
- Log errors.Join contents to distinguish synthetic close from real closeErr.
When it happens
Trigger: Server or client drops the socket mid-session; writeLoop/readLoop detects a failed connection and calls close; code calls Close explicitly while requests are in flight, so pending calls get "connection closed" as their error.
Common situations: AppHost process exits or restarts while the Go client is connected; idle timeout or keepalive failure silently ending the session; firewall/network interruption; double-close patterns where the second close reports "connection closed".
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
- not connected to AppHost
- failed to connect to AppHost
- -32000
- -32603
- ADC request ' ' returned an empty response.
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/a429fbebc2a17662.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.CodeGeneration.Go/Resources/transport.go:315
go c.writeLoop()
}
func (c *connection) close(err error) {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
return
}
c.closed = true
pending := c.pending
c.pending = nil
c.mu.Unlock()
closeErr := c.rawConn.Close()
close(c.done)
if err == nil {
err = errors.New("connection closed")
}
if closeErr != nil {
err = errors.Join(err, closeErr)
}
errResp := map[string]any{
"error": map[string]any{
"code": -32000,
"message": err.Error(),
},
}
for _, ch := range pending {
ch <- errResp
}
c.onClose(err)
}
func (c *connection) writeLoop() {View on GitHub (pinned to 25830f84bd)