{"record":{"id":"f4a7393d1fa6f108","repo":"iOfficeAI/OfficeCLI","slug":"batch-input-must-be-a-json-array-got-rootkind-t","errorCode":null,"errorMessage":"Batch input must be a JSON array. Got: {rootKind.ToString().ToLowerInvariant()}. Wrap a single item like [{\"command\":\"get\",\"path\":\"/\"}].","messagePattern":"Batch input must be a JSON array\\. Got: (.+?)\\. Wrap a single item like \\[(.+?)\\]\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/CommandBuilder.Batch.cs","lineNumber":291,"sourceCode":"                && jsonDoc.RootElement.TryGetProperty(\"data\", out var envData)\n                && envData.ValueKind == System.Text.Json.JsonValueKind.Array)\n            {\n                jsonText = envData.GetRawText();\n                jsonDoc.Dispose();\n                jsonDoc = System.Text.Json.JsonDocument.Parse(jsonText);\n            }\n            using var _jsonDocOwner = jsonDoc;\n            var rootKind = jsonDoc.RootElement.ValueKind;\n            if (rootKind != System.Text.Json.JsonValueKind.Array\n                && rootKind != System.Text.Json.JsonValueKind.Null)\n            {\n                // BUG-R7-10: when the batch input is a JSON object/string/etc.\n                // (not an array), Deserialize<List<BatchItem>> threw a generic\n                // JsonException whose message exposed the C# generic type name\n                // (`System.Collections.Generic.List`1[OfficeCli.BatchItem]`).\n                // Convert it to a human-friendly error first so AI agents and\n                // humans see a stable, model-agnostic diagnostic.\n                throw new ArgumentException(\n                    $\"Batch input must be a JSON array. Got: {rootKind.ToString().ToLowerInvariant()}. \"\n                    + \"Wrap a single item like [{\\\"command\\\":\\\"get\\\",\\\"path\\\":\\\"/\\\"}].\");\n            }\n            if (jsonDoc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Array)\n            {\n                int ri = 0;\n                foreach (var elem in jsonDoc.RootElement.EnumerateArray())\n                {\n                    if (elem.ValueKind == System.Text.Json.JsonValueKind.Object)\n                    {\n                        var unknown = new List<string>();\n                        foreach (var prop in elem.EnumerateObject())\n                        {\n                            if (!BatchItem.KnownFields.Contains(prop.Name))\n                                unknown.Add(prop.Name);\n                        }\n                        if (unknown.Count > 0)\n                            throw new ArgumentException($\"batch item[{ri}]: unknown field(s) {string.Join(\", \", unknown.Select(f => $\"\\\"{f}\\\"\"))}. Valid fields: {string.Join(\", \", BatchItem.KnownFields)}\");","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/CommandBuilder.Batch.cs#L273-L309","documentation":"Thrown when the batch JSON root (after any envelope auto-unwrap) is neither an array nor null. The pre-validation at line 282-294 converts what used to be a generic JsonException exposing the C# type name into a stable, model-agnostic ArgumentException naming the actual root kind and how to fix it (wrap a single item in an array).","triggerScenarios":"`officecli batch file.docx --commands '{\"command\":\"get\"}'` (bare object), or `--commands '\"hello\"'` (string), or `--commands '42'` (number). After envelope unwrap the root kind is object/string/number, not array/null.","commonSituations":"An agent emits a single command as an object instead of a one-element array; a hand-written JSON uses an object root; the input was a dump envelope whose `data` was not an array; copy-paste dropped the enclosing `[ ]`.","solutions":["Wrap the item(s) in a JSON array: `[{\"command\":\"get\",\"path\":\"/\"}]`.","If piping from `dump --json`, the envelope auto-unwrap handles `{\"data\":[...]}`; ensure data is an array.","Validate the root kind before sending: `jq type ops.json` should report `array`."],"exampleFix":"// before\n{\"command\":\"get\",\"path\":\"/\"}\n\n// after\n[{\"command\":\"get\",\"path\":\"/\"}]","handlingStrategy":"validation","validationCode":"// Pre-check the batch root kind.\nusing var d = System.Text.Json.JsonDocument.Parse(jsonText);\nif (d.RootElement.ValueKind != System.Text.Json.JsonValueKind.Array\n    && d.RootElement.ValueKind != System.Text.Json.JsonValueKind.Null)\n    throw new ArgumentException(\"batch input must be a JSON array; wrap single items in []\");","typeGuard":"// Guard: root is array (after optional envelope unwrap).\nstatic bool IsBatchArrayRoot(string json)\n{\n    try\n    {\n        using var d = System.Text.Json.JsonDocument.Parse(json);\n        var k = d.RootElement.ValueKind;\n        if (k == System.Text.Json.JsonValueKind.Object\n            && d.RootElement.TryGetProperty(\"data\", out var data)\n            && data.ValueKind == System.Text.Json.JsonValueKind.Array) return true;\n        return k == System.Text.Json.JsonValueKind.Array || k == System.Text.Json.JsonValueKind.Null;\n    }\n    catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Always emit batch ops as an array, even for a single command.","Validate with `jq type` (expect `array`) in shell pipelines before batch."],"tags":["json","batch","validation","schema"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}