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

  1. Map your source value to one of: true, false, yes, no, 1, 0 (case-insensitive).
  2. If the value is genuinely free text, use type=string instead of type=boolean.
  3. 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

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


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/2fe6c39129a54167. Report an issue: GitHub.