can1357/oh-my-pi · error · HTTPException

invalid json: {exc}

Error message

invalid json: {exc}

What it means

_json_body parses the request body as JSON for all write endpoints (post_comment, open_pull_request, request_reviewers, etc.). If request.json() raises for any reason, the proxy returns HTTP 400 'invalid json: <detail>'. A non-dict JSON value (array, string, number) gets a separate 'json body must be an object' error.

Source

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

        return JSONResponse({"items": [_serialize(c) for c in items]})

    @app.get("/gh/v1/pr_reviews")
    async def list_pr_reviews(request: Request, repo: str, pr_number: int) -> JSONResponse:
        await _authenticate(request)
        github: GitHubClient = request.app.state.github
        try:
            items = await github.list_pr_reviews(repo, pr_number)
        except GitHubError as exc:
            return _gh_error_response(exc)
        return JSONResponse({"items": [_serialize(r) for r in items]})

    # ---- writes ----
    async def _json_body(request: Request) -> dict[str, Any]:
        await _authenticate(request)
        try:
            data = await request.json()
        except Exception as exc:
            raise HTTPException(400, f"invalid json: {exc}") from exc
        if not isinstance(data, dict):
            raise HTTPException(400, "json body must be an object")
        return data

    @app.post("/gh/v1/post_comment")
    async def post_comment(request: Request) -> JSONResponse:
        data = await _json_body(request)
        repo = _require_str(data.get("repo"), "repo")
        number = _require_int(data.get("number"), "number")
        body = _require_str(data.get("body"), "body")
        github: GitHubClient = request.app.state.github
        try:
            info = await github.post_comment(repo, number, body)
        except GitHubError as exc:
            return _gh_error_response(exc)
        return JSONResponse(_serialize(info))

    @app.post("/gh/v1/open_pull_request")

View on GitHub (pinned to 9690622007)

Solutions

  1. Serialize the body with a JSON encoder and send it with Content-Type: application/json
  2. Validate the body parses locally with JSON.parse before sending to isolate client vs server issues
  3. Ensure the full body is transmitted (correct Content-Length, no truncation) and not form-encoded

Example fix

// before
curl -X POST $PROXY/gh/v1/post_comment -d '{issue: 1, body: "hi"}'
// after
curl -X POST $PROXY/gh/v1/post_comment \
  -H 'Content-Type: application/json' \
  -d '{"issue": 1, "body": "hi"}'
Defensive patterns

Strategy: try-catch

Validate before calling

const payload = JSON.stringify(args); // never hand-build JSON
JSON.parse(payload); // optional local round-trip check
if (!args || typeof args !== "object" || Array.isArray(args)) throw new Error("json body must be an object");

Type guard

function isJsonObject(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Try / catch

try {
  const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
  if (res.status === 400) {
    const detail = await res.text();
    throw new Error(`request rejected: ${detail}`); // includes 'invalid json: ...'
  }
} catch (err) { /* handle */ }

Prevention

When it happens

Trigger: POSTing to a /gh/v1 write endpoint with a body that is empty, truncated, not valid JSON (unquoted keys, trailing commas, single quotes), sent with the wrong Content-Type, or with a body consumed/corrupted by middleware.

Common situations: Hand-built request bodies via string concatenation instead of JSON.stringify; double-encoding (sending a JSON string of JSON); empty body on POST; curl without -d quoting; clients sending form-encoded data where JSON is expected.

Understand the failure class

Related errors


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