iOfficeAI/OfficeCLI · error · CliException
plugin_contract_violation
plugin_contract_violation
Error message
Plugin '{resolved.Manifest.Name}' emitted invalid JSON at line #{items.Count}: {ex.Message} What it means
A stdout line from the plugin failed to deserialize as a BatchItem (JsonException). The message includes the line index and the deserializer's error detail, so the offending line and reason are pinpointed.
Source
Thrown at src/officecli/CommandBuilder.Plugins.cs:292
var findings = new List<LintFinding>();
void OnLine(string raw)
{
// Mirrors DumpReaderInvoker: strip per-line UTF-8 BOM so a
// plugin that BOMs every JSONL line passes lint as well as
// it passes invocation.
var line = raw.TrimStart('').Trim();
if (line.Length == 0) return;
if (line[0] == '[')
throw new CliException(
$"Plugin '{resolved.Manifest.Name}' emitted a JSON array; protocol v1 requires JSONL (one BatchItem per line).")
{ Code = "corrupt_batch" };
BatchItem? item;
try { item = JsonSerializer.Deserialize(line, BatchJsonContext.Default.BatchItem); }
catch (JsonException ex)
{
throw new CliException(
$"Plugin '{resolved.Manifest.Name}' emitted invalid JSON at line #{items.Count}: {ex.Message}")
{ Code = "plugin_contract_violation" };
}
if (item is null) return;
items.Add(item);
}
var idle = resolved.Manifest.ResolveIdleTimeout("dump");
var runResult = PluginProcess.Run(new PluginProcess.RunOptions
{
ExecutablePath = resolved.ExecutablePath,
Arguments = new[] { "dump", fixturePath },
IdleTimeoutSeconds = idle,
OnStdoutLine = OnLine,
});
if (PluginProcess.LineCallbackError is CliException ce) throw ce;
if (PluginProcess.LineCallbackError is not null) throw PluginProcess.LineCallbackError;
if (runResult.IdleTimedOut)View on GitHub (pinned to 1ced45e900)
Solutions
- Send all logs to stderr, never stdout (stdout is the JSONL data channel).
- Validate each emitted line parses as BatchItem before publishing.
- Check the reported line number and the JSON error message.
Example fix
// before: plugin prints 'loading fixture...' to stdout, then items
// after: logs go to stderr; stdout is pure JSONL
Console.Error.WriteLine("loading fixture...");
Console.WriteLine(JsonSerializer.Serialize(item)); Defensive patterns
Strategy: try-catch
Validate before calling
// Plugin author: round-trip-validate every line before emitting. var line = JsonSerializer.Serialize(item); JsonSerializer.Deserialize(line, BatchJsonContext.Default.BatchItem); // throws if bad
Try / catch
// Lint runner: collect per-line failures into a single report.
var defects = new List<string>();
try { LintPlugin(plugin, fixture); }
catch (CliException e) when (e.Code == "plugin_contract_violation") {
defects.Add($"{plugin}: {e.Message}");
} Prevention
- stdout is data-only (JSONL); all diagnostics to stderr.
- Round-trip-serialize each BatchItem in the plugin's own tests.
- Watch for stray prints, partial flushes, and crash fragments on stdout.
When it happens
Trigger: A plugin emits a line that is non-empty and not '[' but is not valid JSON or does not match BatchItem's expected shape — e.g. trailing comma, missing required field, a log line leaking into stdout.
Common situations: Stray log/debug text printed to stdout instead of stderr; a BatchItem missing a required property; trailing commas or single quotes; a partial line from a crash.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- corrupt_batch
- Expected "key=value" string in props array
- Unexpected end of JSON
- Expected object or ["key=value"] array for props
- Expected property name
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/182463a4f188b0f3.
Report an issue: GitHub.