{"record":{"id":"70c5eefdd054ea2a","repo":"Flow-Launcher/Flow.Launcher","slug":"json-rpc-plugin-process-stderr","errorCode":null,"errorMessage":"<JSON-RPC plugin process stderr>","messagePattern":"<JSON-RPC plugin process stderr>","errorType":"exception","errorClass":"InvalidDataException","httpStatus":null,"severity":"error","filePath":"Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs","lineNumber":220,"sourceCode":"                // token expire won't instantly trigger the exception, \n                // manually kill process at before\n                await process.WaitForExitAsync(token);\n                await Task.WhenAll(sourceCopyTask, errorCopyTask);\n            }\n            catch (OperationCanceledException)\n            {\n                await sourceBuffer.DisposeAsync();\n                return Stream.Null;\n            }\n\n            switch (sourceBuffer.Length, errorBuffer.Length)\n            {\n                case (0, 0):\n                    const string errorMessage = \"Empty JSON-RPC Response.\";\n                    Context.API.LogWarn(ClassName, errorMessage);\n                    break;\n                case (_, not 0):\n                    throw new InvalidDataException(Encoding.UTF8.GetString(errorBuffer.ToArray())); // The process has exited with an error message\n            }\n\n            sourceBuffer.Seek(0, SeekOrigin.Begin);\n\n            return sourceBuffer;\n        }\n\n        public override async Task<List<Result>> QueryAsync(Query query, CancellationToken token)\n        {\n            var request = new JsonRPCRequestModel(RequestId++,\n                \"query\",\n                new object[]\n                {\n                    query.Search\n                },\n                Settings?.Inner);\n\n            var output = await RequestAsync(request, token);","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/Flow-Launcher/Flow.Launcher/blob/7fc63b07bbaa10a0f0b2601487913fcba9ed24b0/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs#L202-L238","documentation":"Thrown as InvalidDataException carrying the raw UTF-8 stderr of the child JSON-RPC plugin process. The v1 JsonRPCPlugin captures the plugin executable's stdout and stderr into separate buffers via CopyToAsync, and when the process exits with a non-empty errorBuffer (case (_, not 0)), the buffered stderr text is re-thrown. This is the primary channel through which a misbehaving/buggy plugin process surfaces its crash message to Flow Launcher's host.","triggerScenarios":"The plugin child process writes anything to stderr before or while exiting (an uncaught exception, a Python traceback, a Node stack trace, a failed assertion). Specifically: sourceBuffer.Length is anything AND errorBuffer.Length is not 0 after process.WaitForExitAsync completes. This includes the case where the plugin wrote both valid stdout and an error to stderr — error wins.","commonSituations":"Plugin written in Python/Node/Go crashes on startup (missing dependency, wrong runtime version); plugin throws an unhandled exception during query handling that gets dumped to stderr; plugin executable path points to a binary whose required runtime is not installed; plugin logs a warning to stderr then continues (this would still throw even though the plugin is otherwise healthy, since v1 treats any stderr as fatal).","solutions":["Read the message text — it IS the plugin's stderr output, so it names the actual failing module/line in the plugin.","Verify the plugin's declared executable and runtime exist (e.g. python.exe on PATH, node version) by running the plugin's command manually.","Check the plugin's WorkingDirectory and language interpreter config in its plugin.json.","If the plugin intentionally logs non-fatal diagnostics to stderr, this v1 host cannot distinguish them from fatal errors — switch the plugin to the V2 protocol or have it write logs to a file instead.","Update or reinstall the offending plugin from the plugin manifest."],"exampleFix":"// before (plugin v1 — any stderr is fatal)\nimport sys\nprint('loading model...', file=sys.stderr)  // triggers the throw\n\n// after — keep stderr clean, log to a file\nimport logging\nlogging.basicConfig(filename='plugin.log')\nlogging.info('loading model...')","handlingStrategy":"try-catch","validationCode":"// Validate the plugin executable and runtime before invoking\nvar psi = new ProcessStartInfo(pluginExecutable) { RedirectStandardError = true };\nif (!File.Exists(psi.FileName)) return; // guard before RequestAsync","typeGuard":"null","tryCatchPattern":"try { var results = await plugin.QueryAsync(query, token); }\ncatch (InvalidDataException ex) when (ex.Message.Contains(\"JSON-RPC\"))\n{\n    Context.API.LogException(ClassName, $\"Plugin {plugin.Name} stderr: {ex.Message}\", ex);\n    return new List<Result>(); // graceful empty results\n}","preventionTips":["Treat the plugin's stderr as fatal in v1 — ensure plugins log diagnostics to a file, not stderr.","Pin plugin runtime versions in plugin.json and verify they are installed.","Smoke-test plugins by running their executable directly before enabling them.","Set a query timeout via the CancellationToken so a hung plugin process is killed."],"tags":["json-rpc","plugin","subprocess","stderr","v1-plugin"],"backgroundTag":null,"analyzedSha":"7fc63b07bbaa10a0f0b2601487913fcba9ed24b0","analyzedAt":"2026-08-13T14:54:20.914Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}