{"record":{"id":"e429b3640ca9d7bc","repo":"microsoft/aspire","slug":"already-connected-to-apphost-backchannel","errorCode":null,"errorMessage":"Already connected to AppHost backchannel.","messagePattern":"Already connected to AppHost backchannel\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Backchannel/AppHostCliBackchannel.cs","lineNumber":313,"sourceCode":"        }\n\n        logger.LogWarning(\"Timed out waiting for backchannel reconnection\");\n    }\n\n    public Task ConnectAsync(string socketPath, int retryCount, CancellationToken cancellationToken)\n        => ConnectAsync(socketPath, autoReconnect: false, retryCount: retryCount, cancellationToken);\n\n    public async Task ConnectAsync(string socketPath, bool autoReconnect, int retryCount, CancellationToken cancellationToken)\n    {\n        try\n        {\n            using var activity = profilingTelemetry.StartBackchannelConnect(socketPath, autoReconnect, retryCount);\n\n            lock (_lock)\n            {\n                if (_rpcTaskCompletionSource.Task.IsCompleted && !_rpcTaskCompletionSource.Task.IsFaulted)\n                {\n                    throw new InvalidOperationException(ErrorStrings.AlreadyConnectedToBackchannel);\n                }\n            }\n\n            _socketPath = socketPath;\n            _autoReconnect = autoReconnect;\n            _cancellationToken = cancellationToken;\n            lock (_lock)\n            {\n                _disconnectTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);\n            }\n\n            var connectingLogLevel = retryCount % 10 == 0 ? LogLevel.Debug : LogLevel.Trace;\n            logger.Log(connectingLogLevel, \"Connecting to AppHost backchannel at {SocketPath} (autoReconnect={AutoReconnect}, retryCount={RetryCount})\", socketPath, autoReconnect, retryCount);\n            var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);\n            var endpoint = new UnixDomainSocketEndPoint(socketPath);\n            activity.AddBackchannelSocketConnectStartEvent();\n            await socket.ConnectAsync(endpoint, cancellationToken);\n            activity.AddBackchannelSocketConnectedEvent();","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Backchannel/AppHostCliBackchannel.cs#L295-L331","documentation":"AppHostCliBackchannel.ConnectAsync refuses to run when a healthy connection already exists: if the internal _rpcTaskCompletionSource task is completed and not faulted, an active RPC channel is already established. This prevents accidentally creating a second connection over the same instance. Use the existing connection or create a new backchannel instance.","triggerScenarios":"Thrown in ConnectAsync under the _lock when _rpcTaskCompletionSource.Task.IsCompleted && !Task.IsFaulted — i.e. ConnectAsync is called a second time on the same instance while an established, non-faulted connection exists.","commonSituations":"Calling ConnectAsync twice (e.g. in an initialization path and again on a retry/refresh path); concurrent code paths both attempting to connect; reusing a long-lived backchannel instance across commands instead of checking its state first.","solutions":["Check whether the backchannel is already connected before calling ConnectAsync and reuse the existing RPC instead.","Await the existing connection task (the completed _rpc task) rather than reconnecting.","Create a new AppHostCliBackchannel instance if you genuinely need a fresh connection.","Serialize connection setup behind a single init path so ConnectAsync is only invoked once per instance."],"exampleFix":"// before\nawait backchannel.ConnectAsync(socketPath, ...);\n// later, same instance\nawait backchannel.ConnectAsync(socketPath, ...); // throws\n// after\nif (backchannel.RpcTask is not { IsCompleted: true })\n{\n    await backchannel.ConnectAsync(socketPath, ...);\n}","handlingStrategy":"validation","validationCode":"if (backchannel is { } bc && bc.IsConnected)\n{\n    // skip ConnectAsync and reuse the existing connection\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    await backchannel.ConnectAsync(socketPath, ...);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"Already connected\"))\n{\n    // expected: reuse the existing connection instead of reconnecting\n}","preventionTips":["Route all connection setup through a single init path guarded by a connected flag.","Reuse the existing RPC task instead of calling ConnectAsync again on a live instance.","Create a new backchannel instance rather than reconnecting a healthy one."],"tags":["backchannel","connection-state","rpc","cli"],"backgroundTag":"invalid-state-transition","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"}