{"record":{"id":"1777928928121039","repo":"iOfficeAI/OfficeCLI","slug":"batch-item-ni-is-null-each-entry-must-be-a-jso","errorCode":null,"errorMessage":"batch item[{ni}] is null. Each entry must be a JSON object (e.g. {\"command\":\"get\",\"path\":\"/\"}).","messagePattern":"batch item\\[(.+?)\\] is null\\. Each entry must be a JSON object \\(e\\.g\\. (.+?)\\)\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/CommandBuilder.Batch.cs","lineNumber":326,"sourceCode":"                        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)}\");\n                    }\n                    ri++;\n                }\n            }\n\n            var items = System.Text.Json.JsonSerializer.Deserialize<List<BatchItem>>(jsonText, BatchJsonContext.Default.ListBatchItem) ?? new();\n            // NEWLINE-SEMANTICS-V2: strip meta items; rewrite legacy (\\n = soft\n            // break) docx dumps to the v2 encoding before execution.\n            OfficeCli.Core.BatchCompat.PrepareForReplay(items, file.FullName);\n            // BUG-R40-B11: explicit null entries (e.g. `[null]`) deserialize\n            // to a List<BatchItem> with a null slot and trip a NRE deeper in\n            // ExecuteBatchItem. Reject up-front with a recognizable error\n            // pointing at the offending index.\n            for (int ni = 0; ni < items.Count; ni++)\n            {\n                if (items[ni] == null)\n                    throw new ArgumentException(\n                        $\"batch item[{ni}] is null. Each entry must be a JSON object (e.g. {{\\\"command\\\":\\\"get\\\",\\\"path\\\":\\\"/\\\"}}).\");\n            }\n            if (items.Count == 0)\n            {\n                // BUG-R6-07: empty command array previously short-circuited\n                // before the file-existence check, so\n                //   officecli batch /missing.docx --commands '[]' --json\n                // returned a clean zero-result success instead of the\n                // expected file_not_found. Validate the target file\n                // exists first so empty-array semantics match the\n                // non-empty path's diagnostics.\n                if (!file.Exists)\n                    throw new CliException($\"File not found: {file.FullName}\")\n                        { Code = \"file_not_found\" };\n                // BUG-R7-09: in --json mode an empty/null batch input\n                // previously skipped the {\"success\":...,\"data\":{...}}\n                // envelope used by the populated-array path, so AI agents\n                // saw a missing `success` key. Apply the same envelope","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/CommandBuilder.Batch.cs#L308-L344","documentation":"Thrown when a batch array contains an explicit null entry (e.g. `[null]` or `[{...}, null]`). System.Text.Json deserializes these into a List<BatchItem> with a null slot that would later trip a NullReferenceException inside ExecuteBatchItem; the up-front loop at line 323-328 rejects them with a recognizable, index-pointed ArgumentException (tagged BUG-R40-B11).","triggerScenarios":"`officecli batch file.docx --commands '[null]'` or `[{\"command\":\"get\"}, null]`. The deserialized list has a null slot caught by the ni-loop.","commonSituations":"An agent emits a trailing comma producing a null; a JSON template has an empty placeholder; programmatic array construction appends null on an error branch; JSON.stringify of an array with a null element.","solutions":["Remove null entries from the batch array; every element must be a JSON object.","Filter nulls before writing: `jq '[.[] | select(. != null)]' ops.json`.","Ensure the producer never appends null on its error/fallback paths."],"exampleFix":"// before\n[{\"command\":\"get\"}, null]\n\n// after\n[{\"command\":\"get\"}]","handlingStrategy":"validation","validationCode":"// Strip null entries before deserializing/sending.\nusing var d = System.Text.Json.JsonDocument.Parse(jsonText);\nif (d.RootElement.EnumerateArray().Any(e => e.ValueKind == System.Text.Json.JsonValueKind.Null))\n    throw new ArgumentException(\"batch array contains null entries\");","typeGuard":"// Guard: no null slots.\nstatic bool NoNullSlots(string json)\n{\n    try\n    {\n        using var d = System.Text.Json.JsonDocument.Parse(json);\n        return !d.RootElement.EnumerateArray().Any(e => e.ValueKind == System.Text.Json.JsonValueKind.Null);\n    }\n    catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Never append null on fallback branches when building the ops array programmatically.","Filter with `jq '[.[] | select(. != null)]'` in pipelines."],"tags":["json","batch","validation","null","known-bug"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}