larksuite/cli · error · ValueError

Row must be >= 1: {row}

Error message

Row must be >= 1: {row}

What it means

format_cell() in lark_sheet_range.py validates that a row index is a positive 1-based integer before converting (row, col) to an A1 notation cell reference like "B3". Sheets use 1-based rows, so row 0 or negative rows cannot map to a valid A1 cell. The library raises ValueError immediately to prevent emitting a malformed range string to the Sheets API.

Source

Thrown at skills/lark-sheets/scripts/lark_sheet_range.py:139

        _, start_row, start_col = start
        _, end_col = end
        if max_row is None:
            raise ValueError(f"Range needs a maximum row: {range_ref}")
        end_row = max(start_row, max_row)
    elif start[0] == "cell" and end[0] == "row":
        _, start_row, start_col = start
        _, end_row = end
        if max_col is None:
            raise ValueError(f"Range needs a maximum column: {range_ref}")
        end_col = max(start_col, max_col)
    else:
        raise ValueError(f"Invalid A1 range: {range_ref}")
    return RangeBounds(min(start_row, end_row), min(start_col, end_col), max(start_row, end_row), max(start_col, end_col))


def format_cell(row: int, col: int) -> str:
    if row < 1:
        raise ValueError(f"Row must be >= 1: {row}")
    return f"{index_to_col(col)}{row}"


def format_range(start_row: int, start_col: int, end_row: int, end_col: int) -> str:
    bounds = RangeBounds(
        min(start_row, end_row),
        min(start_col, end_col),
        max(start_row, end_row),
        max(start_col, end_col),
    )
    start = format_cell(bounds.start_row, bounds.start_col)
    end = format_cell(bounds.end_row, bounds.end_col)
    return start if start == end else f"{start}:{end}"


def iter_cells(bounds: RangeBounds):
    for row in range(bounds.start_row, bounds.end_row + 1):
        for col in range(bounds.start_col, bounds.end_col + 1):

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Pass a 1-based row: add 1 to any 0-based index before calling format_cell.
  2. Clamp or validate computed rows: if row < 1, fix the loop/slice bounds that produced it instead of coercing.
  3. If you truly need an open-ended range, use format_range with explicit bounds or pass a column-only boundary the range formatter supports, rather than row 0.

Example fix

// before
row_idx = rows.index(target)  # 0-based
ref = format_cell(row_idx, col)  # ValueError: Row must be >= 1: 0

// after
ref = format_cell(rows.index(target) + 1, col)  # 1-based A1 row
Defensive patterns

Strategy: validation

Validate before calling

def safe_format_cell(row, col):
    if not isinstance(row, int) or row < 1:
        raise ValueError(f"row must be a 1-based int >= 1, got {row!r}")
    return format_cell(row, col)

Type guard

def is_valid_row(row) -> bool:
    return isinstance(row, int) and not isinstance(row, bool) and row >= 1

Try / catch

try:
    ref = format_cell(row, col)
except ValueError:
    ref = format_cell(max(row, 1), col)  # or fix the index source

Prevention

When it happens

Trigger: Calling format_cell(row, col) with row < 1, e.g. format_cell(0, 1) or format_cell(-1, 5), typically from format_range or a caller passing a computed row that was decremented to 0.

Common situations: Developers converting 0-based array indices to A1 notation and forgetting the +1; off-by-one loops that decrement the row before formatting; slicing list rows with [start-1:end] and passing the adjusted index instead of the 1-based one.

Related errors


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