can1357/oh-my-pi · error · HTTPException

comments[ ] 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).

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.

Appendix: 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)