{"record":{"id":"d4819fdbe6c9b060","repo":"iOfficeAI/OfficeCLI","slug":"invalid-prop-prop-key-is-empty-use-key-val","errorCode":null,"errorMessage":"Invalid --prop '{prop}': key is empty. Use key=value (e.g. --prop name=Title).","messagePattern":"Invalid --prop '(.+?)': key is empty\\. Use key=value \\(e\\.g\\. --prop name=Title\\)\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/CommandBuilder.cs","lineNumber":1274,"sourceCode":"                      + \"\\\"props\\\":{...}}. Run `help batch` for the item schema.\"\n                    : \" Run `help batch` for the JSON item schema.\";\n                throw new InvalidOperationException($\"Unknown command: '{item.Command}'. Valid commands: get, query, set, add, remove, move, swap, view, raw, validate.{batchHint}\");\n        }\n    }\n\n    private static Dictionary<string, string> ParsePropsArray(string[]? props)\n    {\n        var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);\n        foreach (var prop in props ?? Array.Empty<string>())\n        {\n            var eqIdx = prop.IndexOf('=');\n            // BUG-R40-B12: previously `eqIdx > 0` silently dropped both\n            // `--prop =value` (empty key, eqIdx==0) and `--prop key`\n            // (no equals, eqIdx==-1). Surface the empty-key form as a\n            // hard error so AI callers don't waste a turn wondering why\n            // their property had no effect.\n            if (eqIdx == 0)\n                throw new ArgumentException(\n                    $\"Invalid --prop '{prop}': key is empty. Use key=value (e.g. --prop name=Title).\");\n            if (eqIdx > 0)\n            {\n                var key = prop[..eqIdx];\n                var value = prop[(eqIdx + 1)..];\n                // CONSISTENCY(text-escape-boundary): C-style escape resolution\n                // (\\\\n, \\\\t, \\\\r, \\\\\\\\) is a CLI-input concern only. The shell\n                // gives us the literal four-character sequence `\\\\n` which a\n                // user typing `--prop text='line1\\\\nline2'` plainly wants as\n                // a newline. Handlers no longer call TextEscape.Resolve\n                // internally — that double-resolution mangled batch JSON\n                // payloads, where `\"text\": \"hello\\\\nworld\"` already arrives\n                // as `hello\\\\nworld` literal after JSON parsing and must NOT\n                // be turned into a newline. Affected keys are the text-valued\n                // props: `text`, `value`, and the row-level `c1…cN` cell-text\n                // shortcuts (so `--prop c1='a\\nb'` breaks the line exactly like\n                // `--prop text=` does); other props (colors, paths, numbers)\n                // are passed through untouched.","sourceCodeStart":1256,"sourceCodeEnd":1292,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/CommandBuilder.cs#L1256-L1292","documentation":"Thrown by ParsePropsArray when a --prop token has an '=' at index 0, i.e. the key is empty (e.g. '--prop =value'). Previously this was silently dropped (BUG-R40-B12), so AI callers wasted turns wondering why their property had no effect; it is now a hard error. The check fires only on the CLI --prop path, not the batch props dict.","triggerScenarios":"Calling 'officecli <cmd> --prop =value'. A shell expansion that left the key empty (e.g. an unset variable before '='). A typo like '--prop =text=Hi'.","commonSituations":"A script builds --prop arguments and a variable for the key is unset, producing '--prop =value'. A user types the prop in the wrong order.","solutions":["Use the form '--prop key=value' with a non-empty key, e.g. --prop text=Hi.","Check any shell variable expansion that produced the key side — ensure it is set and non-empty.","For batch JSON, pass props as an object {\"key\":\"value\"} instead of --prop tokens."],"exampleFix":"# before\nofficecli set --path /Sheet1/A1 --prop =value\n# after\nofficecli set --path /Sheet1/A1 --prop value=hello","handlingStrategy":"validation","validationCode":"// CLI --prop tokens: reject empty key before dispatch\nforeach (var p in props ?? Array.Empty<string>())\n{\n    var eq = p.IndexOf('=');\n    if (eq == 0)\n        throw new ArgumentException($\"Invalid --prop '{p}': empty key. Use key=value.\");\n    if (eq < 0)\n        throw new ArgumentException($\"Invalid --prop '{p}': no '='. Use key=value.\");\n}","typeGuard":"static bool IsValidPropToken(string p)\n{\n    var eq = p.IndexOf('=');\n    return eq > 0;\n}","tryCatchPattern":"try { dict = ParsePropsArray(props); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"Invalid --prop\"))\n{ /* report which token is malformed and ask for key=value */ }","preventionTips":["Always emit --prop as 'key=value' with a non-empty key.","Check shell variable expansion that builds the key side.","Prefer the batch JSON props object over --prop tokens to avoid shell escaping."],"tags":["cli","prop","parse","invalid-value"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}