iOfficeAI/OfficeCLI · error · ArgumentException
Anchor must be a row path like /{sheetName}/row[K], got: {an
Error message
Anchor must be a row path like /{sheetName}/row[K], got: {anchorPath} What it means
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.
Source
Thrown at src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs:192
?? throw new ArgumentException($"Sheet not found: {sheetName}");
var sheetData = GetSheet(worksheet).GetFirstChild<SheetData>()
?? GetSheet(worksheet).AppendChild(new SheetData());
// Resolve --before / --after anchors (same shape as Excel CopyFrom):
// anchor must be /<sheetName>/row[K] in the same sheet.
// CONSISTENCY(zero-based-index): per project convention, position.Index
// is 0-based across all formats (--index 0 = head, --index 1 = before
// 2nd slot). xlsx Row uses a 1-based RowIndex internally, so +1 here
// and let the existing branch keep treating `index` as a 1-based row
// number (which is also what the anchor branch below produces).
int? index = position?.Index.HasValue == true ? position!.Index!.Value + 1 : (int?)null;
if (index == null && position != null && (position.After != null || position.Before != null))
{
int FindAnchorRow(string anchorPath)
{
var aSegs = anchorPath.TrimStart('/').Split('/', 2);
if (aSegs.Length < 2)
throw new ArgumentException(
$"Anchor must be a row path like /{sheetName}/row[K], got: {anchorPath}");
if (!aSegs[0].Equals(sheetName, StringComparison.OrdinalIgnoreCase))
throw new ArgumentException(
$"Anchor sheet '{aSegs[0]}' must match target sheet '{sheetName}'");
var am = Regex.Match(aSegs[1], @"^row\[(\d+)\]$");
if (!am.Success)
throw new ArgumentException(
$"Anchor must be a row path like /{sheetName}/row[K], got: {anchorPath}");
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;
}
View on GitHub (pinned to 1ced45e900)
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.
Example fix
// before: anchor missing the row[K] suffix
handler.Add("/Sheet1", "row", null, new { before = "/Sheet1" }); // malformed
// after: full row path as anchor
handler.Add("/Sheet1", "row", null, new { before = "/Sheet1/row[5]" }); Defensive patterns
Strategy: validation
Validate before calling
// Validate anchor path has the required two segments
static bool IsValidRowAnchor(string? anchor, string sheetName)
{
if (string.IsNullOrEmpty(anchor)) return false;
var segs = anchor.TrimStart('/').Split('/', 2);
return segs.Length >= 2 && !string.IsNullOrEmpty(segs[1]);
} Type guard
static bool IsCompleteRowAnchor(string anchor)
{
if (string.IsNullOrEmpty(anchor)) return false;
var segs = anchor.TrimStart('/').Split('/', 2);
return segs.Length >= 2 && System.Text.RegularExpressions.Regex.IsMatch(segs[1], @"^row\[\d+\]$");
} Try / catch
try
{
handler.Add($"/{sheetName}", "row", new InsertPosition { Before = anchor }, properties);
}
catch (ArgumentException ex) when (ex.Message.Contains("Anchor must be a row path"))
{
// Anchor is missing the row[K] component — reformat it
logger.LogError("Anchor '{Anchor}' must be '/{Sheet}/row[K]'.", anchor, sheetName);
throw;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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]').
Related errors
- Anchor sheet '{aSegs[0]}' must match target sheet '{sheetNam
- invalid_format
- invalid_path
- unsupported_path
- Sheet not found: {sheetName}
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/1deeadfd66ad3899.
Report an issue: GitHub.