{"record":{"id":"21163f9059b78306","repo":"iOfficeAI/OfficeCLI","slug":"plugin-error","errorCode":"plugin_error","errorMessage":"Format-handler plugin '{_plugin.Manifest.Name}' reported error on {command ?? msgType}: {msg}","messagePattern":"Format-handler plugin '(.+?)' reported error on (.+?): (.+?)","errorType":"exception","errorClass":"CliException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/Plugins/FormatHandlerSession.cs","lineNumber":257,"sourceCode":"                }\n\n                if (reply is null)\n                {\n                    _broken = true;\n                    throw new CliException(\n                        $\"Format-handler plugin '{_plugin.Manifest.Name}' reply is not a JSON object. First chars: \\\"{Truncate(line, 80)}\\\".\")\n                    { Code = \"protocol_mismatch\" };\n                }\n\n                var replyType = reply[\"msg_type\"]?.GetValue<string>() ?? \"\";\n                if (replyType == \"ok\")\n                    return reply[\"result\"];\n                if (replyType == \"error\")\n                {\n                    var err = reply[\"error\"]?.AsObject();\n                    var code = err?[\"code\"]?.GetValue<string>() ?? \"plugin_error\";\n                    var msg = err?[\"message\"]?.GetValue<string>() ?? \"(no message)\";\n                    throw new CliException(\n                        $\"Format-handler plugin '{_plugin.Manifest.Name}' reported error on {command ?? msgType}: {msg}\")\n                    { Code = code };\n                }\n                _broken = true;\n                throw new CliException(\n                    $\"Format-handler plugin '{_plugin.Manifest.Name}' replied with unknown msg_type '{replyType}'.\")\n                { Code = \"protocol_mismatch\" };\n            }\n            catch (IOException ex)\n            {\n                _broken = true;\n                throw new CliException(\n                    $\"Format-handler plugin '{_plugin.Manifest.Name}' stdin/stdout I/O failed: {ex.Message}\", ex)\n                { Code = \"plugin_stream_closed\" };\n            }\n        }\n    }\n","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/Plugins/FormatHandlerSession.cs#L239-L275","documentation":"Thrown when the plugin sends a well-formed error envelope: {\"msg_type\":\"error\",\"error\":{\"code\":\"...\",\"message\":\"...\"}}. This is the normal error-reporting channel — the plugin intentionally declined or failed the operation. The CliException's Code is set from the plugin's error.code field (defaulting to plugin_error), and the message field carries the plugin's human-readable explanation.","triggerScenarios":"FormatHandlerSession.SendRaw reads a reply where reply[\"msg_type\"] == \"error\". The code is extracted from reply[\"error\"][\"code\"] (or \"plugin_error\" if absent); the message from reply[\"error\"][\"message\"] (or \"(no message)\"). The session is NOT marked _broken — this is a recoverable per-command error, not a protocol failure.","commonSituations":"Plugin can't open a corrupted file and reports an error. Plugin encounters an unsupported feature in the target format. Plugin's external dependency (LibreOffice, a native library) is missing. Plugin rejects an invalid command argument. The error.code propagates as the CliException Code, so downstream error handling can branch on it.","solutions":["Read the CliException Message — it contains the plugin's explanation after the colon.","Read the CliException Code — it carries the plugin's error.code, which may be more specific than plugin_error (e.g. file_corrupt, missing_dependency).","Fix the root cause the plugin describes: repair the input file, install the dependency, correct the argument.","Unlike protocol_mismatch errors, the session is still usable — you can send the next command without restarting."],"exampleFix":"// before: caller treats all plugin errors as fatal\ntry { session.Send(\"save\", null, args, props); }\ncatch (CliException) { session.Dispose(); throw; }  // overkill for plugin_error\n\n// after: only restart on broken-session codes\ntry { session.Send(\"save\", null, args, props); }\ncatch (CliException ex) {\n    if (session.IsBroken) { session.Dispose(); throw; }\n    // plugin_error is recoverable — log and continue or surface to user\n    Console.Error.WriteLine($\"Plugin reported: {ex.Message} [{ex.Code}]\");\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try\n{\n    var result = session.Send(msgType, command, args, props);\n}\ncatch (CliException ex) when (ex.Code != null && ex.Code != \"plugin_error\" && !session.IsBroken)\n{\n    // Only protocol-level failures poison the session.\n    // A plugin_error means the plugin intentionally declined — session is still usable.\n    throw;\n}\n// For plugin_error specifically:\ncatch (CliException ex) when (ex.Code == \"plugin_error\" || !session.IsBroken)\n{\n    // Recoverable: log the message, surface to user, or fall back.\n    Console.Error.WriteLine($\"Plugin declined: {ex.Message} [{ex.Code}]\");\n}","preventionTips":["Read the CliException.Code — it carries the plugin's error.code, which is more specific than 'plugin_error'.","Distinguish recoverable plugin_error from session-poisoning protocol_mismatch/plugin_stream_closed.","Do NOT dispose the session on plugin_error — it's still healthy for the next command.","Validate command inputs before sending to avoid predictable plugin rejections."],"tags":["plugin","protocol","error-handling","jsonl"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}