iOfficeAI/OfficeCLI · error · ArgumentException

One of --index, --after, or --before is required when moving

Error message

One of --index, --after, or --before is required when moving a sheet

What it means

Thrown when moving a whole sheet (sourcePath with no second segment, e.g. "/Sheet1") but position is null or carries none of Index/After/Before. Unlike element moves where a null position means 'append', a sheet reorder requires an explicit target because the handler refuses to guess a sheet's new location.

Source

Thrown at src/officecli/Handlers/Excel/ExcelHandler.Add.cs:210

            Sheet? afterAnchor = null, beforeAnchor = null;
            if (position?.After != null)
            {
                var anchorName = ExtractAnchorSheetName(position.After);
                afterAnchor = sheets.Elements<Sheet>().FirstOrDefault(s =>
                    string.Equals(s.Name?.Value, anchorName, StringComparison.OrdinalIgnoreCase))
                    ?? throw new ArgumentException($"After anchor not found: {position.After}");
            }
            else if (position?.Before != null)
            {
                var anchorName = ExtractAnchorSheetName(position.Before);
                beforeAnchor = sheets.Elements<Sheet>().FirstOrDefault(s =>
                    string.Equals(s.Name?.Value, anchorName, StringComparison.OrdinalIgnoreCase))
                    ?? throw new ArgumentException($"Before anchor not found: {position.Before}");
            }
            else if (index == null)
            {
                throw new ArgumentException("One of --index, --after, or --before is required when moving a sheet");
            }

            // Self-move guard: moving a sheet after/before itself is a no-op.
            // Removing first detaches sheetEl, then InsertAfterSelf/InsertBeforeSelf
            // throws "parent is null" and the sheet is lost (data loss).
            if (ReferenceEquals(afterAnchor, sheetEl) || ReferenceEquals(beforeAnchor, sheetEl))
                return $"/{sheetName}";

            // localSheetId on <definedName> is a 0-based position into
            // <sheets>; capture the pre-move order so scoped names can be
            // remapped to the sheets' new positions after the reorder.
            var preMoveOrder = sheets.Elements<Sheet>().ToList();

            sheetEl.Remove();

            if (afterAnchor != null)
            {
                afterAnchor.InsertAfterSelf(sheetEl);

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Pass InsertPosition.AtIndex(n) for an absolute 1-based slot.
  2. Pass InsertPosition.AfterElement("/<sheet>") or BeforeElement("/<sheet>") for a relative reorder.
  3. If you meant 'move to end', compute the last index from the sheet count and pass AtIndex(count).

Example fix

// before
h.Move("/Sheet1", null, null); // no target -> throws
// after
h.Move("/Sheet1", null, InsertPosition.AtIndex(0)); // move to first position
Defensive patterns

Strategy: validation

Validate before calling

// Whole-sheet move (no element segment) requires an explicit target.
bool isSheetMove = sourcePath.TrimStart('/').Split('/', 2).Length < 2;
bool hasTarget = pos is { Index: not null } || pos?.After != null || pos?.Before != null;
if (isSheetMove && !hasTarget)
    throw new InvalidOperationException("Sheet reorder requires --index, --after, or --before");

Type guard

static bool HasMoveTarget(InsertPosition? p) =>
    p is { Index: not null } || (p?.After != null) || (p?.Before != null);

Prevention

When it happens

Trigger: h.Move("/Sheet1", null, null); or Move("/Sheet1", null, new InsertPosition()) with all fields null; passing a position object whose Index is null and After/Before are null.

Common situations: Caller assumes whole-sheet Move defaults to append like Add does; refactored code dropped the position argument; CLI invocation omitted --index/--after/--before.

Related errors


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