{"record":{"id":"1deeadfd66ad3899","repo":"iOfficeAI/OfficeCLI","slug":"anchor-must-be-a-row-path-like-sheetname-row-k","errorCode":null,"errorMessage":"Anchor must be a row path like /{sheetName}/row[K], got: {anchorPath}","messagePattern":"Anchor must be a row path like /(.+?)/row\\[K\\], got: (.+?)","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs","lineNumber":192,"sourceCode":"            ?? throw new ArgumentException($\"Sheet not found: {sheetName}\");\n        var sheetData = GetSheet(worksheet).GetFirstChild<SheetData>()\n            ?? GetSheet(worksheet).AppendChild(new SheetData());\n\n        // Resolve --before / --after anchors (same shape as Excel CopyFrom):\n        // anchor must be /<sheetName>/row[K] in the same sheet.\n        // CONSISTENCY(zero-based-index): per project convention, position.Index\n        // is 0-based across all formats (--index 0 = head, --index 1 = before\n        // 2nd slot). xlsx Row uses a 1-based RowIndex internally, so +1 here\n        // and let the existing branch keep treating `index` as a 1-based row\n        // number (which is also what the anchor branch below produces).\n        int? index = position?.Index.HasValue == true ? position!.Index!.Value + 1 : (int?)null;\n        if (index == null && position != null && (position.After != null || position.Before != null))\n        {\n            int FindAnchorRow(string anchorPath)\n            {\n                var aSegs = anchorPath.TrimStart('/').Split('/', 2);\n                if (aSegs.Length < 2)\n                    throw new ArgumentException(\n                        $\"Anchor must be a row path like /{sheetName}/row[K], got: {anchorPath}\");\n                if (!aSegs[0].Equals(sheetName, StringComparison.OrdinalIgnoreCase))\n                    throw new ArgumentException(\n                        $\"Anchor sheet '{aSegs[0]}' must match target sheet '{sheetName}'\");\n                var am = Regex.Match(aSegs[1], @\"^row\\[(\\d+)\\]$\");\n                if (!am.Success)\n                    throw new ArgumentException(\n                        $\"Anchor must be a row path like /{sheetName}/row[K], got: {anchorPath}\");\n                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","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs#L174-L210","documentation":"Thrown by the FindAnchorRow helper inside AddRow when the --before/--after anchor path does not have at least two segments after trimming the leading slash. An anchor must be a full row path of the form '/<sheetName>/row[K]'. If the split produces fewer than two segments (e.g. the path is just '/Sheet1' with no row[K] suffix), the anchor is malformed and cannot be resolved to a row number.","triggerScenarios":"Passing a --before or --after anchor that is missing the row[K] component, such as '/Sheet1' instead of '/Sheet1/row[5]'. The anchor path must include both the sheet name and the row bracket notation for the resolver to extract a row index.","commonSituations":"A user passing a sheet path instead of a row path as the anchor; a script that builds the anchor by concatenating the sheet name but forgetting to append '/row[K]'; confusion between the parent path format ('/<sheet>') and the anchor format ('/<sheet>/row[K]').","solutions":["Format the anchor as '/<sheetName>/row[K]' — e.g. '/Sheet1/row[5]'.","For --before: the new row takes the row[K] slot, pushing the original row[K] to row[K+1].","For --after: the new row is inserted immediately after row[K].","Use --index N as an alternative to anchors for simple positional insertion."],"exampleFix":"// before: anchor missing the row[K] suffix\nhandler.Add(\"/Sheet1\", \"row\", null, new { before = \"/Sheet1\" }); // malformed\n\n// after: full row path as anchor\nhandler.Add(\"/Sheet1\", \"row\", null, new { before = \"/Sheet1/row[5]\" });","handlingStrategy":"validation","validationCode":"// Validate anchor path has the required two segments\nstatic bool IsValidRowAnchor(string? anchor, string sheetName)\n{\n    if (string.IsNullOrEmpty(anchor)) return false;\n    var segs = anchor.TrimStart('/').Split('/', 2);\n    return segs.Length >= 2 && !string.IsNullOrEmpty(segs[1]);\n}","typeGuard":"static bool IsCompleteRowAnchor(string anchor)\n{\n    if (string.IsNullOrEmpty(anchor)) return false;\n    var segs = anchor.TrimStart('/').Split('/', 2);\n    return segs.Length >= 2 && System.Text.RegularExpressions.Regex.IsMatch(segs[1], @\"^row\\[\\d+\\]$\");\n}","tryCatchPattern":"try\n{\n    handler.Add($\"/{sheetName}\", \"row\", new InsertPosition { Before = anchor }, properties);\n}\ncatch (ArgumentException ex) when (ex.Message.Contains(\"Anchor must be a row path\"))\n{\n    // Anchor is missing the row[K] component — reformat it\n    logger.LogError(\"Anchor '{Anchor}' must be '/{Sheet}/row[K]'.\", anchor, sheetName);\n    throw;\n}","preventionTips":["Format anchors as '/<sheetName>/row[K]' — always include the row bracket suffix.","Use --index N (0-based) as a simpler alternative when you do not need relative positioning.","When building anchors programmatically: $\"/{sheetName}/row[{rowNumber}]\".","Validate the anchor has at least two path segments before calling Add."],"tags":["excel","add-row","anchor-path","invalid-format","argument-validation"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}