larksuite/cli · error

end %q must be at or after start %q

Error message

end %q must be at or after start %q

What it means

After both cell references in a range parse successfully, the parser verifies the end cell is not before the start cell in either row or column order. If the end row or end column is smaller than the start's, it rejects the range. The error is intermediate and gets wrapped into a typed validation error with flag/param context by callers.

Source

Thrown at shortcuts/sheets/lark_sheet_workbook.go:2124

	rangeStr = strings.TrimSpace(rangeStr)
	if rangeStr == "" {
		return 0, 0, 0, 0, fmt.Errorf("empty range") //nolint:forbidigo // intermediate error; callers wrap it into a typed validation error with flag/param context
	}
	parts := strings.SplitN(rangeStr, ":", 2)
	if len(parts) == 1 {
		col, row, ok := splitCellRef(parts[0])
		if !ok {
			return 0, 0, 0, 0, fmt.Errorf("invalid cell ref %q", parts[0]) //nolint:forbidigo // intermediate error; callers wrap it into a typed validation error with flag/param context
		}
		return col, row, col, row, nil
	}
	startCol, startRow, ok1 := splitCellRef(parts[0])
	endCol, endRow, ok2 := splitCellRef(parts[1])
	if !ok1 || !ok2 {
		return 0, 0, 0, 0, fmt.Errorf("unsupported range form %q (need rectangular A1:B2)", rangeStr) //nolint:forbidigo // intermediate error; callers wrap it into a typed validation error with flag/param context
	}
	if endRow < startRow || endCol < startCol {
		return 0, 0, 0, 0, fmt.Errorf("end %q must be at or after start %q", parts[1], parts[0]) //nolint:forbidigo // intermediate error; callers wrap it into a typed validation error with flag/param context
	}
	return startCol, startRow, endCol, endRow, nil
}

// mergeWorkbookCreateStyle merges one cell_styles op's style map into a cell.
// cell_styles / border_styles are nested submaps: they are deep-merged one level
// (field-wise, last write wins) so overlapping cell_styles ops accumulate fields
// rather than the later op's submap wholesale-replacing the earlier one. A fresh
// submap is allocated each merge so the op.Style shared across the range's cells
// is never mutated.
func mergeWorkbookCreateStyle(cell interface{}, style map[string]interface{}) {
	if len(style) == 0 {
		return
	}
	m, ok := cell.(map[string]interface{})
	if !ok {
		return
	}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Swap the two cells so the top-left cell comes first, e.g. use 'A1:B2' instead of 'B2:A1'.
  2. If the range is computed, order start/end by (row, column) before formatting the string.
  3. Normalize with min/max on column index and row number in generating code.

Example fix

// before
--range "B2:A1"
// after
--range "A1:B2"
Defensive patterns

Strategy: validation

Validate before calling

func normalizeRange(s string) (string, error) {
	parts := strings.Split(s, ":")
	if len(parts) != 2 { return "", fmt.Errorf("need A1:B2 form") }
	if cellKey(parts[0]) > cellKey(parts[1]) { parts[0], parts[1] = parts[1], parts[0] }
	return parts[0] + ":" + parts[1], nil
}

Type guard

func isOrderedRange(s string) bool {
	p := strings.Split(s, ":")
	return len(p) == 2 && cellKey(p[0]) <= cellKey(p[1])
}

Prevention

When it happens

Trigger: Passing an inverted range to a sheet workbook shortcut, e.g. 'B2:A1' (end column before start column) or 'A5:A1' (end row above start row).

Common situations: Users selecting a range bottom-right to top-left in their head, or programmatically generating ranges where start/end were computed in the wrong order.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/f89616e504b70f1a. Report an issue: GitHub.