iOfficeAI/OfficeCLI · error · ArgumentException
Invalid row index {rowIdx}. Valid row range is 1-1048576.
Error message
Invalid row index {rowIdx}. Valid row range is 1-1048576. What it means
Excel worksheets support exactly 1,048,576 rows (2^20). AddRow resolves a 1-based target row index from --index/--before/--after anchors, or appends after the last existing RowIndex. When the resolved index falls outside [1, 1048576] this guard throws before any structural change, because emitting row[1048577+] produces an xlsx that Excel refuses to open. The append branch previously computed `lastRow + 1` silently past the ceiling; this mirrors the Set path's bound check so the overflow surfaces as a clean error at Add time.
Source
Thrown at src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs:219
return (int)uint.Parse(am.Groups[1].Value);
}
// For row insertion, --before /Sheet1/row[5] means "the new row
// takes the row[5] slot, original row[5] shifts to row[6]". So
// resolved index == anchor row number. --after /Sheet1/row[5]
// means index == anchor + 1.
if (position.Before != null) index = FindAnchorRow(position.Before);
else index = FindAnchorRow(position.After!) + 1;
}
var rowIdx = index ?? ((int)(sheetData.Elements<Row>().LastOrDefault()?.RowIndex?.Value ?? 0) + 1);
// Excel's row space tops out at 1048576 (2^20). The append branch
// above silently produced row[1048577+] when row[1048576] already
// existed, writing a file Excel rejects on open. Mirror the Set
// path's bound check (ExcelHandler.Set.cs row index guard) so the
// overflow surfaces as a clean invalid_value at Add time.
if (rowIdx < 1 || rowIdx > 1048576)
throw new ArgumentException(
$"Invalid row index {rowIdx}. Valid row range is 1-1048576.");
// If inserting at an existing position, shift everything at/below it
// down. Gate only on "inserting at a position" (index set), NOT on the
// presence of cell data at/below — sheet-level structures (CF / merge /
// dataValidation) anchored on still-empty cells must shift too. Mirrors
// AddCol, which calls ShiftColumnsRight on every positional insert
// (CONSISTENCY(add-row-col-shift)). When nothing sits at/below rowIdx this
// is a harmless no-op.
// Validate all props BEFORE the structural shift (same atomicity rule
// as AddCol): a height/outline parse failure after ShiftRowsDown left
// the shift applied even though the add reported an error.
double? parsedRowHeight = null;
if (properties.TryGetValue("height", out var addRowHeight) && !string.IsNullOrWhiteSpace(addRowHeight))
parsedRowHeight = ParseRowHeightPoints(addRowHeight);
byte? parsedRowOutline = null;
if (properties.TryGetValue("outline", out var addRowOutline)
|| properties.TryGetValue("outlinelevel", out addRowOutline)View on GitHub (pinned to 1ced45e900)
Solutions
- Read the sheet's last used row first; if it is already 1048576 you cannot append — create a second sheet and continue there.
- When using --index/--before/--after, compute the resulting 1-based row number (Index is 0-based internally then +1; Before anchor takes the anchor's slot, After = anchor+1) and confirm it is <= 1048576 before calling Add.
- For data exports approaching the ceiling, shard the data across multiple worksheets rather than relying on a single sheet.
Example fix
// before — appends when the sheet is full → throws
handler.Add("/Sheet1", "row", null, new() { ["cols"] = "3" });
// after — check capacity, spill to a new sheet
var lastRow = LastUsedRowIndex(handler, "/Sheet1");
var target = lastRow >= 1048576 ? "/Sheet2" : "/Sheet1";
if (target == "/Sheet2" && SheetExists(handler, "/Sheet2") == false)
handler.Add("/", "sheet", null, new() { ["name"] = "Sheet2" });
handler.Add(target, "row", null, new() { ["cols"] = "3" }); Defensive patterns
Strategy: validation
Validate before calling
// Compute the 1-based row index AddRow will resolve, then bound-check it.
int ResolveTargetRow(ExcelHandler h, string sheet, InsertPosition pos)
{
// Append case: last used row + 1
var last = h.Query($"/{sheet}/row").Max(r => /* RowIndex */ 0);
int idx = pos?.Index.HasValue == true ? pos.Index.Value + 1 : last + 1;
if (pos?.After != null) idx = int.Parse(Regex.Match(pos.After, @"\[(\d+)\]").Groups[1].Value) + 1;
if (pos?.Before != null) idx = int.Parse(Regex.Match(pos.Before, @"\[(\d+)\]").Groups[1].Value);
return idx;
}
if (ResolveTargetRow(h, sheet, pos) is < 1 or > 1048576)
throw new InvalidOperationException("Row index would exceed Excel's 1048576 ceiling."); Try / catch
try { h.Add(sheet, "row", pos, props); }
catch (ArgumentException ex) when (ex.Message.Contains("Valid row range is 1-1048576"))
{ /* spill to a new sheet or skip */ } Prevention
- Track the running row count in your export loop and switch sheets before hitting 1048576.
- Never assume Append will always succeed on a sheet near capacity.
- Treat 1048576 as a hard ceiling, not a soft target.
When it happens
Trigger: Add("/Sheet1","row",position,props) where position.Index+1 > 1048576 (e.g. InsertPosition.AtIndex(1048575) → index+1=1048576 is fine, but 1048576 → 1048577 throws); appending to a sheet whose last RowIndex is already 1048576 (auto-append computes lastRow+1=1048577); a --after anchor on /Sheet1/row[1048576] (resolves to 1048576+1).
Common situations: Bulk-export pipelines that stream rows into a single sheet without a ceiling check; batch replays onto a sheet that is already full; scripts that compute `--index` from an external counter that has overrun the Excel grid.
Related errors
- Invalid 'outline' value: '{addRowOutline}'. Expected an inte
- Invalid 'cols' value: '{colsStr}'. Expected a positive integ
- Unrecognized cell parent path segment '{cellSegments[1]}'. E
- Invalid cell reference: '{cellRef}'
- --prop shift={shiftVal} not valid for add cell. Use 'right'
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/133c399a32bc64c0.
Report an issue: GitHub.