{"record":{"id":"2fe6c39129a54167","repo":"iOfficeAI/OfficeCLI","slug":"cannot-store-properties-getvalueordefault-value","errorCode":null,"errorMessage":"Cannot store '{properties.GetValueOrDefault(\"value\") ?? properties.GetValueOrDefault(\"text\")}' as boolean; value must be true/false, yes/no, or 1/0. Use type=string to keep the literal text.","messagePattern":"Cannot store '(.+?)' as boolean; value must be true/false, yes/no, or 1/0\\. Use type=string to keep the literal text\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs","lineNumber":474,"sourceCode":"            if (shiftDir == \"right\")\n                ShiftCellsRightInRow(cellSheetData, (uint)shiftRow, shiftColIdx);\n            else\n                ShiftCellsDownInColumn(cellSheetData, shiftCol, shiftRow);\n        }\n\n        // Atomicity: validate a type=boolean value BEFORE FindOrCreateCell\n        // appends the cell to the sheet. A throw AFTER the cell is created\n        // used to leave a corrupt <c t=\"b\"><v>garbage</v></c> persisted on\n        // disk (real Excel then refuses the file, 0x800A03EC) even though the\n        // Add reported an error. The later in-switch check stays as a\n        // defense-in-depth guard.\n        {\n            var upfrontType = properties.GetValueOrDefault(\"type\")?.ToLowerInvariant();\n            var upfrontValue = (properties.GetValueOrDefault(\"value\")\n                ?? properties.GetValueOrDefault(\"text\"))?.Trim().ToLowerInvariant();\n            if ((upfrontType is \"boolean\" or \"bool\") && !string.IsNullOrEmpty(upfrontValue)\n                && upfrontValue is not (\"true\" or \"false\" or \"yes\" or \"no\" or \"1\" or \"0\"))\n                throw new ArgumentException(\n                    $\"Cannot store '{properties.GetValueOrDefault(\"value\") ?? properties.GetValueOrDefault(\"text\")}' as boolean; \" +\n                    \"value must be true/false, yes/no, or 1/0. Use type=string to keep the literal text.\");\n        }\n\n        // Atomicity: FindOrCreateCell materializes a <c> stub if the cell did\n        // not exist. A validation throw further down (bad textRotation, bad\n        // color, bad merge ref, ...) must not leave that stub — or the value\n        // already written into it — persisted while the command reports\n        // Error/exit 1. Capture pre-existence, then roll the new cell back on\n        // any throw. Mirrors the Set-side rollback (ExcelHandler.Set.cs).\n        var cellPreExisted = cellSheetData.Elements<Row>()\n            .SelectMany(r => r.Elements<Cell>())\n            .Any(c => string.Equals(c.CellReference?.Value, cellRef, StringComparison.OrdinalIgnoreCase));\n\n        var cell = FindOrCreateCell(cellSheetData, cellRef);\n        // Clone for rollback of a pre-existing cell (restore original state);\n        // a newly created cell is removed instead (see catch below).\n        var cellBackup = cell.CloneNode(true);","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs#L456-L492","documentation":"An upfront atomicity guard: when type=boolean (or bool) is supplied with a value=/text= that is not one of true/false/yes/no/1/0, this throws BEFORE FindOrCreateCell appends the cell to the sheet. Without it, a later throw would leave a corrupt <c t=\"b\"><v>garbage</v></c> persisted on disk, which makes real Excel refuse the whole file (0x800A03EC) even though the Add reported an error. The later in-switch check stays as defense-in-depth.","triggerScenarios":"Add(\"/Sheet1/A1\",\"cell\",pos,{[\"type\"]=\"boolean\",[\"value\"]=\"hello\"}); value=\"maybe\"; value=\"2\"; value=\"T\" (not a recognized token); type=bool value=\"yep\".","commonSituations":"Coercing a free-text field into a boolean without mapping; passing 'T'/'F' or 'y'/'n' shorthand (only full true/false/yes/no/1/0 are accepted); a templated value that is sometimes non-boolean text.","solutions":["Map your source value to one of: true, false, yes, no, 1, 0 (case-insensitive).","If the value is genuinely free text, use type=string instead of type=boolean.","Add a normalization step before the call that converts your domain booleans to the accepted token set."],"exampleFix":"// before\nhandler.Add(\"/Sheet1/A1\", \"cell\", null, new() { [\"type\"] = \"boolean\", [\"value\"] = flag });\n// after\nvar boolVal = flag.ToLowerInvariant() switch { \"t\" or \"y\" => \"yes\", \"f\" or \"n\" => \"no\", _ => flag };\nhandler.Add(\"/Sheet1/A1\", \"cell\", null, new() { [\"type\"] = \"boolean\", [\"value\"] = boolVal });","handlingStrategy":"validation","validationCode":"static readonly HashSet<string> BoolTokens = new(){\"true\",\"false\",\"yes\",\"no\",\"1\",\"0\"};\nif (props.GetValueOrDefault(\"type\")?.ToLowerInvariant() is \"boolean\" or \"bool\")\n{\n    var v = (props.GetValueOrDefault(\"value\") ?? props.GetValueOrDefault(\"text\"))?.Trim().ToLowerInvariant();\n    if (!string.IsNullOrEmpty(v) && !BoolTokens.Contains(v))\n        throw new ArgumentException(\"value is not a boolean token\");\n}\nh.Add(parentPath, \"cell\", pos, props);","typeGuard":"static bool IsBoolToken(string? s) =>\n    s?.Trim().ToLowerInvariant() is \"true\" or \"false\" or \"yes\" or \"no\" or \"1\" or \"0\";","tryCatchPattern":"try { h.Add(parentPath, \"cell\", pos, props); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"as boolean\"))\n{ /* fall back to type=string for the literal text */ }","preventionTips":["Normalize domain booleans (T/F, y/n) to the accepted token set before the call.","Use type=string when the value is genuinely free text.","Supply value= together with type=boolean so the existing text is overwritten."],"tags":["excel","xlsx","cell","boolean","atomicity","validation"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}