can1357/oh-my-pi · error · HTTPException

comments[{idx}] must be an object

Error message

comments[{idx}] must be an object

What it means

Each element of the `comments` array in a PR review must be an object with path, line, and body. `_require_review_comments` raises this HTTP 400 when an array element is not a JSON object (e.g. a bare string or number).

Source

Thrown at python/robomp/src/proxy/server.py:188


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")
        comment: dict[str, Any] = {"path": path, "line": line, "side": side, "body": body}
        start_line = item.get("start_line")
        if start_line is not None:
            comment["start_line"] = _require_int(start_line, f"comments[{idx}].start_line")
        start_side = item.get("start_side")
        if start_side is not None:
            start_side_str = _require_str(start_side, f"comments[{idx}].start_side")
            if start_side_str not in ("RIGHT", "LEFT"):
                raise HTTPException(400, f"comments[{idx}].start_side must be RIGHT or LEFT")
            comment["start_side"] = start_side_str
        comments.append(comment)
    return comments

View on GitHub (pinned to 9690622007)

Solutions

  1. Send each comment as an object with required keys path (string), line (int), and body (string).
  2. Build comments programmatically: comments.append({"path": p, "line": l, "body": b}) rather than appending raw strings.
  3. Validate the payload shape client-side before the request (see defense type guard).
  4. Check for accidental flattening — e.g. list(dict) yields keys only, not objects.

Example fix

// before
"comments": ["a.py looks wrong"]
// after
"comments": [{"path": "a.py", "line": 1, "body": "a.py looks wrong"}]
Defensive patterns

Strategy: validation

Validate before calling

def check_comments(comments):
    for c in comments:
        if not isinstance(c, dict):
            raise ValueError("each comment must be an object")
        for k in ("path", "body"):
            if not isinstance(c.get(k), str):
                raise ValueError(f"comment.{k} must be a string")
        if not isinstance(c.get("line"), int) or isinstance(c["line"], bool):
            raise ValueError("comment.line must be an int")

Type guard

def is_comment(c: object) -> TypeGuard[dict]:
    return (
        isinstance(c, dict)
        and isinstance(c.get("path"), str)
        and isinstance(c.get("line"), int)
        and not isinstance(c.get("line"), bool)
        and isinstance(c.get("body"), str)
    )

Try / catch

try:
    resp = http.post(f"{base}/pr/{n}/review", json=payload)
    resp.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 400 and "must be an object" in e.response.text:
        idx = int(e.response.text.split("comments[")[1].split("]")[0])
        raise ValueError(f"comments[{idx}] is not an object: {payload['comments'][idx]!r}") from e
    raise

Prevention

When it happens

Trigger: Posting submit_pr_review with comments like ["leave a note"] or ["path a.py", 3] instead of objects such as [{"path": "a.py", "line": 3, "body": "note"}].

Common situations: CLI tools accepting free-text review notes and passing them through unstructured; clients submitting an array of file paths expecting the server to attach a default message; template mistakes dropping the object braces.

Related errors


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