{"record":{"id":"df84a416b0a7d4dc","repo":"iOfficeAI/OfficeCLI","slug":"remove-command-requires-path-field-example","errorCode":null,"errorMessage":"'remove' command requires 'path' field. Example: {\"command\": \"remove\", \"path\": \"/slide[1]/shape[2]\"}","messagePattern":"'remove' command requires 'path' field\\. Example: (.+?)","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/CommandBuilder.cs","lineNumber":1141,"sourceCode":"                    {\n                        \"tsv\" => '\\t',\n                        \"csv\" => ',',\n                        _ => throw new CliException($\"Unknown format: {importFmt}. Use 'csv' or 'tsv'\")\n                            { Code = \"invalid_value\", ValidValues = [\"csv\", \"tsv\"] },\n                    };\n                }\n                var importHeader = props.TryGetValue(\"header\", out var importHdr)\n                    && OfficeCli.Core.ParseHelpers.IsTruthy(importHdr);\n                var importStart = props.TryGetValue(\"start-cell\", out var importSc) && !string.IsNullOrEmpty(importSc)\n                    ? importSc\n                    : props.TryGetValue(\"startcell\", out var importSc2) && !string.IsNullOrEmpty(importSc2)\n                        ? importSc2 : \"A1\";\n                return importXl.Import(importParent, item.Text, importDelim, importHeader, importStart);\n            }\n            case \"remove\":\n            {\n                if (string.IsNullOrEmpty(item.Path))\n                    throw new ArgumentException(\"'remove' command requires 'path' field. Example: {\\\"command\\\": \\\"remove\\\", \\\"path\\\": \\\"/slide[1]/shape[2]\\\"}\");\n                var path = item.Path;\n                OfficeCli.Core.MutationSelectorGuard.EnsureScoped(path, \"remove\");\n                var warning = RemoveWithShiftSupport(handler, path, item.Props);\n                var msg = $\"Removed {path}\";\n                if (warning != null) msg += $\"\\n{warning}\";\n                return msg;\n            }\n            case \"move\":\n            {\n                var path = item.Path ?? \"/\";\n                InsertPosition? movePos = null;\n                if (item.Index.HasValue) movePos = InsertPosition.AtIndex(item.Index.Value);\n                else if (!string.IsNullOrEmpty(item.After)) movePos = InsertPosition.AfterElement(item.After);\n                else if (!string.IsNullOrEmpty(item.Before)) movePos = InsertPosition.BeforeElement(item.Before);\n                // Pass props to the 4-arg Move like the CLI and resident do; the\n                // batch/MCP path previously dropped move-time properties.\n                var resultPath = handler.Move(path, item.To, movePos, props.Count > 0 ? props : null);\n                return $\"Moved to {resultPath}\";","sourceCodeStart":1123,"sourceCodeEnd":1159,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/CommandBuilder.cs#L1123-L1159","documentation":"Thrown by the batch 'remove' command when item.Path is null or empty. The remove operation needs a concrete target element to delete, so an absent path is a hard error rather than a no-op. The message shows the expected JSON shape and is followed (if it passed) by MutationSelectorGuard.EnsureScoped which additionally forbids removing a whole-root path.","triggerScenarios":"A batch item {\"command\":\"remove\"} with no 'path' field, or with 'path':''/'path':null. Also when a caller puts the target under a different key (e.g. 'parent' or 'to') expecting remove to accept it.","commonSituations":"An agent reuses an 'add' item template (which uses parent/type) and only flips the command to 'remove' without adding a 'path'. A query-first workflow that drops the path when zero or many nodes matched earlier.","solutions":["Add a 'path' field pointing at the element to delete, e.g. /slide[1]/shape[2].","Make sure the path is scoped (contains a [index]) — MutationSelectorGuard rejects bare root removal.","If removing by selector, first run 'query' to resolve a concrete indexed path, then pass that path to 'remove'."],"exampleFix":"// before\n{\"command\":\"remove\"}\n// after\n{\"command\":\"remove\",\"path\":\"/slide[1]/shape[2]\"}","handlingStrategy":"validation","validationCode":"if (string.IsNullOrEmpty(item.Path))\n    throw new ArgumentException(\"'remove' requires 'path' pointing at an indexed element, e.g. /slide[1]/shape[2]\");\nif (!item.Path.Contains('['))\n    throw new ArgumentException(\"'remove' path must be scoped (contain an [index]); root removal is not allowed\");","typeGuard":"static bool IsRemovablePath(string? p) =>\n    !string.IsNullOrEmpty(p) && p.Contains('[') && p.Contains(']');","tryCatchPattern":"try { result = Dispatch(item); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"'remove' command requires 'path'\"))\n{ /* log, resolve a path via query, and retry */ }","preventionTips":["Always resolve a concrete indexed path via 'query' before issuing 'remove'.","Never send a remove item without a 'path' field.","Reject whole-root removal at the caller — MutationSelectorGuard will also reject it."],"tags":["batch","remove","missing-field","path"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}