{"record":{"id":"0964126b22904b92","repo":"rocksdanister/lively","slug":"ex-message","errorCode":null,"errorMessage":"ex.Message","messagePattern":"ex\\.Message","errorType":"exception","errorClass":"RpcException","httpStatus":null,"severity":"error","filePath":"src/Lively/Lively/RPC/AppUpdateServer.cs","lineNumber":115,"sourceCode":"                var isRequestedBetaChannel = request.Channel == ReleaseChannel.Beta;\n                var isCurrentBetaChannel = Constants.ApplicationType.IsTestBuild;\n\n                if (isCurrentBetaChannel && isRequestedBetaChannel || !(isCurrentBetaChannel || isRequestedBetaChannel))\n                    return await Task.FromResult(new Empty());\n\n                var (SetupUri, SetupFileName, _) = await updater.GetLatestRelease(isRequestedBetaChannel);\n                var filePath = Path.Combine(Constants.CommonPaths.TempDir, SetupFileName);\n\n                await downloader.DownloadFile(SetupUri, filePath, null, context.CancellationToken);\n                // Run setup in silent mode.\n                Process.Start(filePath, \"/SILENT /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS\");\n                // Inno installer will auto retry, waiting for application exit.\n                App.QuitApp();\n            }\n            catch (Exception ex)\n            {\n                Logger.Error(ex);\n                throw new RpcException(new Status(StatusCode.Internal, ex.Message));\n            }\n            return await Task.FromResult(new Empty());\n        }\n\n        public override Task<UpdateResponse> GetUpdateStatus(Empty _, ServerCallContext context)\n        {\n            return Task.FromResult(new UpdateResponse()\n            {\n                Status = (UpdateStatus)((int)updater.Status),\n                Changelog = string.Empty,\n                Url = updater.LastCheckUri?.OriginalString ?? string.Empty,\n                FileName = updater.LastCheckFileName ?? string.Empty,\n                Version = updater.LastCheckVersion?.ToString() ?? string.Empty,\n                Time = Timestamp.FromDateTime(updater.LastCheckTime.ToUniversalTime()),\n            });\n        }\n\n        public override async Task SubscribeUpdateChecked(Empty _, IServerStreamWriter<Empty> responseStream, ServerCallContext context)","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/rocksdanister/lively/blob/c1036feb664960722e34bf4309042c247d6a909d/src/Lively/Lively/RPC/AppUpdateServer.cs#L97-L133","documentation":"The outer catch-all of SwitchReleaseChannel: any exception inside the method (download failure, Process.Start failure, GetLatestRelease failure, OperationCanceledException on client disconnect, or the MSIX guard from error #26) is logged and re-thrown as RpcException(StatusCode.Internal) with the inner Message as detail. Because it catches System.Exception, the original exception type is lost to the client; only the message string survives.","triggerScenarios":"SwitchReleaseChannel fails at any step: downloader.DownloadFile throws (network/disk/cancellation), Process.Start throws (installer missing or blocked), updater.GetLatestRelease throws, or the MSIX guard (#26) fires first and is re-wrapped here.","commonSituations":"Transient network drop during the installer download. Antivirus/SmartScreen blocking the Inno installer launch. TempDir not writable. Client cancels mid-download. Running on MSIX (this catch wraps error #26 into a generic Internal).","solutions":["Read the gRPC detail (inner.Message) and the server NLog to identify the real failure step.","For download/cancel failures, retry SwitchReleaseChannel once connectivity returns.","For Process.Start failures, confirm the downloaded installer exists at Constants.CommonPaths.TempDir and isn't blocked by AV/SmartScreen."],"exampleFix":"// before\ncatch (Exception ex)\n{\n    Logger.Error(ex);\n    throw new RpcException(new Status(StatusCode.Internal, ex.Message));\n}\n\n// after: map specific causes to fitting status codes and preserve the cause type\ncatch (OperationCanceledException)\n{\n    throw new RpcException(new Status(StatusCode.Cancelled, \"Channel switch cancelled.\"));\n}\ncatch (IOException ex)\n{\n    throw new RpcException(new Status(StatusCode.Unavailable, $\"Download failed: {ex.Message}\"));\n}\ncatch (Exception ex)\n{\n    Logger.Error(ex);\n    var trailers = new Metadata { { \"cause-exception\", ex.GetType().FullName } };\n    throw new RpcException(new Status(StatusCode.Internal, ex.Message), ex, trailers);\n}","handlingStrategy":"try-catch","validationCode":"if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())\n{\n    DialogService.Warn(\"Offline \\u2014 cannot switch release channel now.\");\n    return;\n}\nif (PackageUtil.IsRunningAsPackaged)\n{\n    DialogService.Warn(\"Channel switching requires the desktop build.\");\n    return;\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    await client.SwitchReleaseChannelAsync(req, cancellationToken: cts.Token);\n}\ncatch (RpcException ex) when (ex.StatusCode == StatusCode.Internal)\n{\n    Logger.Error(ex, \"Channel switch failed: {Detail}\", ex.Status.Detail);\n    if (ex.Status.Detail.Contains(\"msix\"))\n    {\n        // prompt user to install the desktop build\n    }\n    else\n    {\n        // schedule a retry once connectivity returns\n    }\n}","preventionTips":["Don't catch Exception just to re-wrap as Internal; map specific failures to fitting gRPC status codes (Unavailable, Cancelled, FailedPrecondition).","Attach the inner exception type via gRPC metadata/trailers so the client can branch without parsing the message string.","Validate preconditions (network, MSIX, disk space) before starting the download so most failures never reach the catch-all."],"tags":["grpc","network","update","rpc","error-handling","csharp"],"backgroundTag":null,"analyzedSha":"c1036feb664960722e34bf4309042c247d6a909d","analyzedAt":"2026-08-13T13:03:12.648Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}