{"record":{"id":"133c399a32bc64c0","repo":"iOfficeAI/OfficeCLI","slug":"invalid-row-index-rowidx-valid-row-range-is-1-1","errorCode":null,"errorMessage":"Invalid row index {rowIdx}. Valid row range is 1-1048576.","messagePattern":"Invalid row index (.+?)\\. Valid row range is 1-1048576\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs","lineNumber":219,"sourceCode":"                return (int)uint.Parse(am.Groups[1].Value);\n            }\n            // For row insertion, --before /Sheet1/row[5] means \"the new row\n            // takes the row[5] slot, original row[5] shifts to row[6]\". So\n            // resolved index == anchor row number. --after /Sheet1/row[5]\n            // means index == anchor + 1.\n            if (position.Before != null) index = FindAnchorRow(position.Before);\n            else index = FindAnchorRow(position.After!) + 1;\n        }\n\n        var rowIdx = index ?? ((int)(sheetData.Elements<Row>().LastOrDefault()?.RowIndex?.Value ?? 0) + 1);\n\n        // Excel's row space tops out at 1048576 (2^20). The append branch\n        // above silently produced row[1048577+] when row[1048576] already\n        // existed, writing a file Excel rejects on open. Mirror the Set\n        // path's bound check (ExcelHandler.Set.cs row index guard) so the\n        // overflow surfaces as a clean invalid_value at Add time.\n        if (rowIdx < 1 || rowIdx > 1048576)\n            throw new ArgumentException(\n                $\"Invalid row index {rowIdx}. Valid row range is 1-1048576.\");\n\n        // If inserting at an existing position, shift everything at/below it\n        // down. Gate only on \"inserting at a position\" (index set), NOT on the\n        // presence of cell data at/below — sheet-level structures (CF / merge /\n        // dataValidation) anchored on still-empty cells must shift too. Mirrors\n        // AddCol, which calls ShiftColumnsRight on every positional insert\n        // (CONSISTENCY(add-row-col-shift)). When nothing sits at/below rowIdx this\n        // is a harmless no-op.\n        // Validate all props BEFORE the structural shift (same atomicity rule\n        // as AddCol): a height/outline parse failure after ShiftRowsDown left\n        // the shift applied even though the add reported an error.\n        double? parsedRowHeight = null;\n        if (properties.TryGetValue(\"height\", out var addRowHeight) && !string.IsNullOrWhiteSpace(addRowHeight))\n            parsedRowHeight = ParseRowHeightPoints(addRowHeight);\n        byte? parsedRowOutline = null;\n        if (properties.TryGetValue(\"outline\", out var addRowOutline)\n            || properties.TryGetValue(\"outlinelevel\", out addRowOutline)","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs#L201-L237","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before — appends when the sheet is full → throws\nhandler.Add(\"/Sheet1\", \"row\", null, new() { [\"cols\"] = \"3\" });\n// after — check capacity, spill to a new sheet\nvar lastRow = LastUsedRowIndex(handler, \"/Sheet1\");\nvar target = lastRow >= 1048576 ? \"/Sheet2\" : \"/Sheet1\";\nif (target == \"/Sheet2\" && SheetExists(handler, \"/Sheet2\") == false)\n    handler.Add(\"/\", \"sheet\", null, new() { [\"name\"] = \"Sheet2\" });\nhandler.Add(target, \"row\", null, new() { [\"cols\"] = \"3\" });","handlingStrategy":"validation","validationCode":"// Compute the 1-based row index AddRow will resolve, then bound-check it.\nint ResolveTargetRow(ExcelHandler h, string sheet, InsertPosition pos)\n{\n    // Append case: last used row + 1\n    var last = h.Query($\"/{sheet}/row\").Max(r => /* RowIndex */ 0);\n    int idx = pos?.Index.HasValue == true ? pos.Index.Value + 1 : last + 1;\n    if (pos?.After != null) idx = int.Parse(Regex.Match(pos.After, @\"\\[(\\d+)\\]\").Groups[1].Value) + 1;\n    if (pos?.Before != null) idx = int.Parse(Regex.Match(pos.Before, @\"\\[(\\d+)\\]\").Groups[1].Value);\n    return idx;\n}\nif (ResolveTargetRow(h, sheet, pos) is < 1 or > 1048576)\n    throw new InvalidOperationException(\"Row index would exceed Excel's 1048576 ceiling.\");","typeGuard":null,"tryCatchPattern":"try { h.Add(sheet, \"row\", pos, props); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"Valid row range is 1-1048576\"))\n{ /* spill to a new sheet or skip */ }","preventionTips":["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."],"tags":["excel","xlsx","row-overflow","validation","excel-limit"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}