can1357/oh-my-pi · error · ValueError

{field}[{index}] must be a string

Error message

{field}[{index}] must be a string

What it means

`_optional_str_list` accepts a field that is absent, a single string, or an array of strings. When an array is supplied, every element must be a string; this error fires naming the offending index, reporting exactly which element was invalid (`field[index]`).

Source

Thrown at python/omp-rpc/src/omp_rpc/protocol.py:243

def _optional_str_list(payload: JsonObject, field: str) -> tuple[str, ...]:
    """Parse an optional string-or-array-of-strings field.

    The agent's `systemPrompt` (and similar) became `string[]` server-side
    when multi-prompt support landed. Older daemons still emit a bare string,
    so we accept either shape. Returns an empty tuple when the field is
    absent or null.
    """
    value = payload.get(field)
    if value is None:
        return ()
    if isinstance(value, str):
        return (value,)
    if isinstance(value, list):
        items: list[str] = []
        for index, item in enumerate(value):
            if not isinstance(item, str):
                raise ValueError(f"{field}[{index}] must be a string")
            items.append(item)
        return tuple(items)
    raise ValueError(f"{field} must be a string or an array of strings")


def _optional_bool(payload: JsonObject, field: str) -> bool | None:
    value = payload.get(field)
    if value is None:
        return None
    if not isinstance(value, bool):
        raise ValueError(f"{field} must be a boolean")
    return value


def _optional_int(payload: JsonObject, field: str) -> int | None:
    value = payload.get(field)
    if value is None:
        return None

View on GitHub (pinned to 9690622007)

Solutions

  1. Look at the index in the message, coerce that element to a string, and re-parse
  2. Normalize the array before parsing: [str(x) for x in value]
  3. Fix the producer so every element is a JSON string

Example fix

# before
payload = {"todos": ["a", 2, "b"]}
state = parse_session_state(payload)  # ValueError: todos[1] must be a string
# after
payload = {"todos": [str(x) for x in ["a", 2, "b"]]}
state = parse_session_state(payload)
Defensive patterns

Strategy: validation

Validate before calling

def ensure_str_list_items(payload: dict, field: str) -> None:
    value = payload.get(field)
    if isinstance(value, list):
        for i, item in enumerate(value):
            if not isinstance(item, str):
                raise TypeError(f"{field}[{i}] must be a string, got {item!r}")

ensure_str_list_items(payload, "todos")
parse_session_state(payload)

Type guard

def is_str_or_str_list(value: object) -> bool:
    if isinstance(value, str):
        return True
    return isinstance(value, list) and all(isinstance(x, str) for x in value)

Try / catch

try:
    state = parse_session_state(payload)
except ValueError as e:
    if "must be a string" in str(e):
        field = str(e).split("[")[0]
        payload[field] = [str(x) for x in payload.get(field, [])]
        state = parse_session_state(payload)
    else:
        raise

Prevention

When it happens

Trigger: parse_session_state receives a list field where one element is a number, null, bool, or nested object — e.g. `["a", 3, "b"]` fails at index 1.

Common situations: A producer mixes numeric and string IDs in an array; JSON from another language serializes mixed-type lists; a fixture was hand-edited and one entry lost its quotes.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/deb51852f1f393ab. Report an issue: GitHub.