iOfficeAI/OfficeCLI · error · CliException
protocol_mismatch
protocol_mismatch
Error message
Format-handler plugin '{_plugin.Manifest.Name}' wrote non-JSON to stdout (a JSONL envelope was expected): {ex.Message}. First chars: "{Truncate(line, 80)}". This is a plugin bug — diagnostic output must go to stderr or --log-file, not stdout. What it means
Thrown when the plugin's stdout reply line is not valid JSON — System.Text.Json's JsonNode.Parse throws a JsonException. The raw parser message (e.g. "'d' is an invalid start of a value") is opaque, so this wrapper names the plugin, shows the first 80 characters of what was actually written, and explains that stdout is a JSONL-only channel. The session is marked _broken because a protocol-shape failure poisons the framing contract.
Source
Thrown at src/officecli/Core/Plugins/FormatHandlerSession.cs:235
// Any protocol-shape failure poisons the session: §6.7 lists
// "malformed reply" as a broken-state trigger, so we mark
// _broken before throwing so the next Send fast-fails instead
// of trying to write into a session whose protocol invariants
// are gone. We also catch JsonException explicitly: the raw
// System.Text.Json message ("'d' is an invalid start of a
// value...") is opaque to users — wrap it in a clear
// `protocol_mismatch` envelope that names the plugin and
// shows a preview of what it actually wrote.
JsonObject? reply;
try
{
reply = JsonNode.Parse(line)?.AsObject();
}
catch (JsonException ex)
{
_broken = true;
throw new CliException(
$"Format-handler plugin '{_plugin.Manifest.Name}' wrote non-JSON to stdout (a JSONL envelope was expected): {ex.Message}. " +
$"First chars: \"{Truncate(line, 80)}\". This is a plugin bug — diagnostic output must go to stderr or --log-file, not stdout.")
{ Code = "protocol_mismatch" };
}
if (reply is null)
{
_broken = true;
throw new CliException(
$"Format-handler plugin '{_plugin.Manifest.Name}' reply is not a JSON object. First chars: \"{Truncate(line, 80)}\".")
{ Code = "protocol_mismatch" };
}
var replyType = reply["msg_type"]?.GetValue<string>() ?? "";
if (replyType == "ok")
return reply["result"];
if (replyType == "error")
{View on GitHub (pinned to 1ced45e900)
Solutions
- Inspect the 'First chars' preview in the error message to identify exactly what was written to stdout.
- Redirect the plugin's logging framework to stderr: Python `logging.basicConfig(stream=sys.stderr)`, Node `console.error(...)`, etc.
- Add a `--log-file` flag to the plugin so verbose output goes to a file, not stdout/stderr.
- Test the plugin in isolation: pipe its stdout through `jq .` to confirm every line is a valid JSON object.
Example fix
// before: Python plugin logs to stdout by default
import logging
logging.basicConfig(level=logging.DEBUG) # defaults to stderr, but...
# ...or worse:
print(f"Processing slide {n}") # goes to stdout, breaks framing
// after
import logging, sys
logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)
sys.stderr.write(f"Processing slide {n}\n") Defensive patterns
Strategy: try-catch
Try / catch
try
{
var result = session.Send(msgType, command, args, props);
}
catch (CliException ex) when (ex.Code == "protocol_mismatch")
{
// The 'First chars' preview in ex.Message shows what the plugin wrote to stdout.
// The session is broken — respawn it after fixing the plugin's output routing.
session.Dispose();
throw; // or respawn + retry after plugin fix
} Prevention
- Configure plugin logging to stderr from the start: Python logging.basicConfig(stream=sys.stderr).
- Run plugin stdout through `jq .` in a test harness to verify every line is a JSON object.
- Never use print()/printf/console.log for diagnostics in a plugin — use stderr equivalents.
- Add a `--log-file` option to the plugin for verbose output.
When it happens
Trigger: FormatHandlerSession.SendRaw reads a line that starts with a non-JSON character or contains malformed JSON. JsonNode.Parse(line) throws inside the try block at line 228-231. Typical cause: the plugin printed a stack trace, a progress message, a banner, or a bare string to stdout.
Common situations: Plugin uses a logging library (Python logging, log4j, serde) configured with a StreamHandler/ConsoleAppender on stdout by default. Plugin prints a version banner or 'Starting...' line on startup. Plugin's framework (e.g. argparse, click) echoes to stdout instead of stderr. A crash traceback lands on stdout before the reply.
Related errors
- plugin_error
- corrupt_batch
- plugin_contract_violation
- Expected "key=value" string in props array
- Unexpected end of JSON
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/606616e625834b4e.
Report an issue: GitHub.