iOfficeAI/OfficeCLI · error · ArgumentException
Cannot store '{properties.GetValueOrDefault("value") ?? prop
Error message
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. What it means
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.
Source
Thrown at src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs:474
if (shiftDir == "right")
ShiftCellsRightInRow(cellSheetData, (uint)shiftRow, shiftColIdx);
else
ShiftCellsDownInColumn(cellSheetData, shiftCol, shiftRow);
}
// Atomicity: validate a type=boolean value BEFORE FindOrCreateCell
// appends the cell to the sheet. A throw AFTER the cell is created
// used to leave a corrupt <c t="b"><v>garbage</v></c> persisted on
// disk (real Excel then refuses the file, 0x800A03EC) even though the
// Add reported an error. The later in-switch check stays as a
// defense-in-depth guard.
{
var upfrontType = properties.GetValueOrDefault("type")?.ToLowerInvariant();
var upfrontValue = (properties.GetValueOrDefault("value")
?? properties.GetValueOrDefault("text"))?.Trim().ToLowerInvariant();
if ((upfrontType is "boolean" or "bool") && !string.IsNullOrEmpty(upfrontValue)
&& upfrontValue is not ("true" or "false" or "yes" or "no" or "1" or "0"))
throw new ArgumentException(
$"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.");
}
// Atomicity: FindOrCreateCell materializes a <c> stub if the cell did
// not exist. A validation throw further down (bad textRotation, bad
// color, bad merge ref, ...) must not leave that stub — or the value
// already written into it — persisted while the command reports
// Error/exit 1. Capture pre-existence, then roll the new cell back on
// any throw. Mirrors the Set-side rollback (ExcelHandler.Set.cs).
var cellPreExisted = cellSheetData.Elements<Row>()
.SelectMany(r => r.Elements<Cell>())
.Any(c => string.Equals(c.CellReference?.Value, cellRef, StringComparison.OrdinalIgnoreCase));
var cell = FindOrCreateCell(cellSheetData, cellRef);
// Clone for rollback of a pre-existing cell (restore original state);
// a newly created cell is removed instead (see catch below).
var cellBackup = cell.CloneNode(true);View on GitHub (pinned to 1ced45e900)
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.
Example fix
// before
handler.Add("/Sheet1/A1", "cell", null, new() { ["type"] = "boolean", ["value"] = flag });
// after
var boolVal = flag.ToLowerInvariant() switch { "t" or "y" => "yes", "f" or "n" => "no", _ => flag };
handler.Add("/Sheet1/A1", "cell", null, new() { ["type"] = "boolean", ["value"] = boolVal }); Defensive patterns
Strategy: validation
Validate before calling
static readonly HashSet<string> BoolTokens = new(){"true","false","yes","no","1","0"};
if (props.GetValueOrDefault("type")?.ToLowerInvariant() is "boolean" or "bool")
{
var v = (props.GetValueOrDefault("value") ?? props.GetValueOrDefault("text"))?.Trim().ToLowerInvariant();
if (!string.IsNullOrEmpty(v) && !BoolTokens.Contains(v))
throw new ArgumentException("value is not a boolean token");
}
h.Add(parentPath, "cell", pos, props); Type guard
static bool IsBoolToken(string? s) =>
s?.Trim().ToLowerInvariant() is "true" or "false" or "yes" or "no" or "1" or "0"; Try / catch
try { h.Add(parentPath, "cell", pos, props); }
catch (ArgumentException ex) when (ex.Message.Contains("as boolean"))
{ /* fall back to type=string for the literal text */ } Prevention
- 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.
When it happens
Trigger: Add("/Sheet1/A1","cell",pos,{["type"]="boolean",["value"]="hello"}); value="maybe"; value="2"; value="T" (not a recognized token); type=bool value="yep".
Common situations: 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.
Related errors
- Cannot store '{cell.CellValue?.Text}' as boolean; value must
- Unrecognized cell parent path segment '{cellSegments[1]}'. E
- Invalid cell reference: '{cellRef}'
- --prop shift={shiftVal} not valid for add cell. Use 'right'
- Literal braces '{...}' around a formula create an Excel-reje
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/2fe6c39129a54167.
Report an issue: GitHub.