{"record":{"id":"e1c9604402d9336d","repo":"microsoft/aspire","slug":"no-client-connection-available-for-callback-invocation","errorCode":null,"errorMessage":"No client connection available for callback invocation","messagePattern":"No client connection available for callback invocation","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.RemoteHost/JsonRpcCallbackInvoker.cs","lineNumber":35,"sourceCode":"\n    /// <summary>\n    /// Sets the JSON-RPC connection to use for invoking callbacks.\n    /// </summary>\n    /// <param name=\"clientRpc\">The JSON-RPC connection.</param>\n    public void SetConnection(JsonRpc clientRpc)\n    {\n        _clientRpc = clientRpc;\n    }\n\n    /// <inheritdoc />\n    public bool IsConnected => _clientRpc != null;\n\n    /// <inheritdoc />\n    public async Task<TResult> InvokeAsync<TResult>(string callbackId, JsonNode? args, CancellationToken cancellationToken = default)\n    {\n        if (_clientRpc == null)\n        {\n            throw new InvalidOperationException(\"No client connection available for callback invocation\");\n        }\n\n        using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);\n        cts.CancelAfter(s_callbackTimeout);\n\n        try\n        {\n            return await _clientRpc.InvokeWithCancellationAsync<TResult>(\n                \"invokeCallback\",\n                [callbackId, args],\n                cts.Token).ConfigureAwait(false);\n        }\n        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)\n        {\n            throw new TimeoutException($\"Callback '{callbackId}' timed out after {s_callbackTimeout.TotalSeconds}s\");\n        }\n    }\n","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.RemoteHost/JsonRpcCallbackInvoker.cs#L17-L53","documentation":"JsonRpcCallbackInvoker lets server-side code call back into the connected client by callbackId. This InvalidOperationException is thrown when InvokeAsync is called but no client JsonRpc connection has been attached to the invoker yet (or it was detached), so there is no transport to send the 'invokeCallback' request on.","triggerScenarios":"Server-side code invoking a registered callback (via InvokeAsync of the callback invoker) before a client connection is assigned to _clientRpc, or after the client connection was cleared/disposed.","commonSituations":"Callback fired during startup before the dashboard/client finished connecting; a background task outliving the client session and attempting a callback after disconnect; wiring the invoker without registering the client RPC instance.","solutions":["Ensure the client JsonRpc instance is attached to the callback invoker before any code path can trigger callbacks.","Guard callback invocations behind client-connected state or defer them until the connection is established.","Treat client disconnect as terminal for pending callbacks: cancel or drop them instead of invoking.","If a callback is optional, check availability (e.g. a TryInvoke or null clientRpc) and skip gracefully."],"exampleFix":"// before\nawait callbackInvoker.InvokeAsync<object?>(\"refreshResources\", args);\n\n// after\nif (callbackInvoker.HasClientConnection)\n{\n    await callbackInvoker.InvokeAsync<object?>(\"refreshResources\", args);\n}","handlingStrategy":"type-guard","validationCode":"// Check the invoker is connected before invoking:\nif (invoker.ClientRpc is null) return; // or queue the callback","typeGuard":"bool CanInvoke(JsonRpcCallbackInvoker invoker) => invoker.HasClientConnection;","tryCatchPattern":"try { await invoker.InvokeAsync<object?>(callbackId, args); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"No client connection\"))\n{ /* drop or defer callback */ }","preventionTips":["Attach the client JsonRpc to the invoker during startup, before enabling any feature that fires callbacks.","Cancel in-flight callback work when the client disconnects.","Make callbacks best-effort with explicit availability checks."],"tags":["json-rpc","callback","lifecycle"],"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"}