can1357/oh-my-pi · error · HTTPException

comments[{idx}].side must be RIGHT or LEFT

Error message

comments[{idx}].side must be RIGHT or LEFT

What it means

Review comment `side` is optional and defaults to RIGHT, but if provided (after `str()` coercion) it must be exactly RIGHT or LEFT. Any other value raises this HTTP 400 for the offending comment index.

Source

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

        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


def _pool_dir(cfg: Settings, repo: str) -> Path:
    _validate_repo_name(repo)
    return Path(cfg.workspace_root) / "_pool" / repo.replace("/", "__")

View on GitHub (pinned to 9690622007)

Solutions

  1. Send exactly "RIGHT" or "LEFT" (uppercase), or omit `side` to get the RIGHT default.
  2. Normalize casing client-side: side = raw.strip().upper() and only send if it is RIGHT or LEFT.
  3. Drop PENDING or other extra enum values — this proxy only supports RIGHT/LEFT.
  4. Ensure numeric fields are not accidentally landing in the side slot of the payload.

Example fix

// before
{"path": "a.py", "line": 3, "body": "x", "side": "right"}
// after
{"path": "a.py", "line": 3, "body": "x", "side": "RIGHT"}
Defensive patterns

Strategy: validation

Validate before calling

side = raw.strip().upper() if raw else "RIGHT"
if side not in ("RIGHT", "LEFT"):
    raise ValueError(f"side must be RIGHT or LEFT, got {raw!r}")
comment["side"] = side

Type guard

def is_valid_side(v: object) -> TypeGuard[str]:
    return v in ("RIGHT", "LEFT")

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 ".side must be" in e.response.text:
        raise ValueError("comment side must be exactly 'RIGHT' or 'LEFT'") from e
    raise

Prevention

When it happens

Trigger: submit_pr_review with a comment where `side` is e.g. "right" (lowercase), "R", "SIDE_RIGHT", or a numeric value whose str() is not RIGHT/LEFT.

Common situations: Clients using GitHub's full side vocabulary (e.g. "RIGHT" vs custom casing); confusion with GraphQL input enums; copy-pasted values from other APIs that accept PENDING as a side.

Related errors


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