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

  1. Send all logs to stderr, never stdout (stdout is the JSONL data channel).
  2. Validate each emitted line parses as BatchItem before publishing.
  3. 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

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

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/182463a4f188b0f3. Report an issue: GitHub.