can1357/oh-my-pi · error · HTTPException
invalid '{field}': must be array of strings
Error message
invalid '{field}': must be array of strings What it means
`_optional_str_list` validates optional list-valued fields (reviewers, labels, assignees). The value must be absent/null or a JSON array whose every element is a string; anything else raises this HTTP 400 naming the offending field.
Source
Thrown at python/robomp/src/proxy/server.py:176
tag = _require_str(value, "tag")
if not _RELEASE_TAG_RE.fullmatch(tag):
raise HTTPException(400, "invalid tag")
return tag
def _optional_slot_uid(value: Any) -> int | None:
if value is None:
return None
if not isinstance(value, int) or isinstance(value, bool) or not (0 < value < 65536):
raise HTTPException(400, "missing/invalid 'slot_uid'")
return value
def _optional_str_list(value: Any, field: str) -> list[str] | None:
if value is None:
return None
if not isinstance(value, list) or not all(isinstance(v, str) for v in value):
raise HTTPException(400, f"invalid '{field}': must be array of strings")
return list(value)
def _require_review_comments(value: Any) -> list[dict[str, Any]]:
if value is None:
return []
if not isinstance(value, list):
raise HTTPException(400, "missing/invalid 'comments'")
comments: list[dict[str, Any]] = []
for idx, item in enumerate(value):
if not isinstance(item, dict):
raise HTTPException(400, f"comments[{idx}] must be an object")
path = _require_str(item.get("path"), f"comments[{idx}].path")
line = _require_int(item.get("line"), f"comments[{idx}].line")
body = _require_str(item.get("body"), f"comments[{idx}].body")
side = str(item.get("side") or "RIGHT")
if side not in ("RIGHT", "LEFT"):
raise HTTPException(400, f"comments[{idx}].side must be RIGHT or LEFT")View on GitHub (pinned to 9690622007)
Solutions
- Always wrap single values in an array: ["alice"] not "alice".
- Ensure every element is a string — stringify numbers (e.g. issue numbers) before adding to label/assignee lists.
- Filter nulls/undefined from the array client-side before sending.
- If you want no change, omit the field entirely rather than sending an empty non-list value.
Example fix
// before
{"reviewers": "alice"}
// after
{"reviewers": ["alice"]} Defensive patterns
Strategy: validation
Validate before calling
def as_str_list(v):
if v is None:
return None
if isinstance(v, str):
v = [v]
if not isinstance(v, list) or not all(isinstance(x, str) for x in v):
raise ValueError(f"{field} must be an array of strings")
return [x for x in v if x is not None] Type guard
def is_str_list(v: object) -> TypeGuard[list[str]]:
return isinstance(v, list) and all(isinstance(x, str) for x in v) Try / catch
try:
resp = http.post(f"{base}/issues/{n}/labels", json={"labels": labels})
resp.raise_for_status()
except httpx.HTTPStatusError as e:
if e.response.status_code == 400 and "must be array of strings" in e.response.text:
raise ValueError("labels/reviewers/assignees must be list[str]") from e
raise Prevention
- Always wrap single values in a list before sending list-typed fields
- Coerce numeric/None entries to strings or filter them out first
- Use typed client helpers (dataclasses/pydantic) so the shape is enforced at build time
- Log the serialized payload when debugging 400s to spot type drift
When it happens
Trigger: Calling request_reviewers, add_issue_labels, or add_assignees with a field that is a single string instead of an array (e.g. "alice" instead of ["alice"]), an array containing numbers/nulls, or a non-list object.
Common situations: Clients passing a comma-separated string from CLI input; scripts building payloads with one element and forgetting to wrap it in a list; arrays containing None entries after filtering failures.
Related errors
- missing/invalid 'slot_uid'
- missing/invalid 'comments'
- comments[{idx}] must be an object
- comments[{idx}].side must be RIGHT or LEFT
- comments[{idx}].start_side must be RIGHT or LEFT
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/21774b467bbc1e7a.
Report an issue: GitHub.