{"record":{"id":"17034acc02740fc8","repo":"can1357/oh-my-pi","slug":"invalid-json-exc","errorCode":null,"errorMessage":"invalid json: {exc}","messagePattern":"invalid json: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"python/robomp/src/proxy/server.py","lineNumber":656,"sourceCode":"        return JSONResponse({\"items\": [_serialize(c) for c in items]})\n\n    @app.get(\"/gh/v1/pr_reviews\")\n    async def list_pr_reviews(request: Request, repo: str, pr_number: int) -> JSONResponse:\n        await _authenticate(request)\n        github: GitHubClient = request.app.state.github\n        try:\n            items = await github.list_pr_reviews(repo, pr_number)\n        except GitHubError as exc:\n            return _gh_error_response(exc)\n        return JSONResponse({\"items\": [_serialize(r) for r in items]})\n\n    # ---- writes ----\n    async def _json_body(request: Request) -> dict[str, Any]:\n        await _authenticate(request)\n        try:\n            data = await request.json()\n        except Exception as exc:\n            raise HTTPException(400, f\"invalid json: {exc}\") from exc\n        if not isinstance(data, dict):\n            raise HTTPException(400, \"json body must be an object\")\n        return data\n\n    @app.post(\"/gh/v1/post_comment\")\n    async def post_comment(request: Request) -> JSONResponse:\n        data = await _json_body(request)\n        repo = _require_str(data.get(\"repo\"), \"repo\")\n        number = _require_int(data.get(\"number\"), \"number\")\n        body = _require_str(data.get(\"body\"), \"body\")\n        github: GitHubClient = request.app.state.github\n        try:\n            info = await github.post_comment(repo, number, body)\n        except GitHubError as exc:\n            return _gh_error_response(exc)\n        return JSONResponse(_serialize(info))\n\n    @app.post(\"/gh/v1/open_pull_request\")","sourceCodeStart":638,"sourceCodeEnd":674,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/proxy/server.py#L638-L674","documentation":"_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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Serialize the body with a JSON encoder and send it with Content-Type: application/json","Validate the body parses locally with JSON.parse before sending to isolate client vs server issues","Ensure the full body is transmitted (correct Content-Length, no truncation) and not form-encoded"],"exampleFix":"// before\ncurl -X POST $PROXY/gh/v1/post_comment -d '{issue: 1, body: \"hi\"}'\n// after\ncurl -X POST $PROXY/gh/v1/post_comment \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"issue\": 1, \"body\": \"hi\"}'","handlingStrategy":"try-catch","validationCode":"const payload = JSON.stringify(args); // never hand-build JSON\nJSON.parse(payload); // optional local round-trip check\nif (!args || typeof args !== \"object\" || Array.isArray(args)) throw new Error(\"json body must be an object\");","typeGuard":"function isJsonObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const res = await fetch(url, { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify(body) });\n  if (res.status === 400) {\n    const detail = await res.text();\n    throw new Error(`request rejected: ${detail}`); // includes 'invalid json: ...'\n  }\n} catch (err) { /* handle */ }","preventionTips":["Always build bodies with JSON.stringify, never string concatenation","Send Content-Type: application/json on every write request","Validate the payload is a plain object before sending","Check for empty/truncated bodies in intermediaries (proxies, retry wrappers)"],"tags":["http-400","json","request-body"],"backgroundTag":"invalid-json-body","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}