{"record":{"id":"84a66a5be69c2c47","repo":"abi/screenshot-to-code","slug":"invalid-side-json-error-msg-line-error-line","errorCode":null,"errorMessage":"Invalid {side} JSON: {error.msg} (line {error.lineno}, column {error.colno})","messagePattern":"Invalid (.+?) JSON: (.+?) \\(line (.+?), column (.+?)\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/routes/evals.py","lineNumber":181,"sourceCode":"    left_summary: str\n    right_summary: str\n    left_value: object | None\n    right_value: object | None\n\n\nclass OpenAIInputCompareResponse(BaseModel):\n    common_prefix_items: int\n    left_item_count: int\n    right_item_count: int\n    difference: OpenAIInputCompareDifferenceResponse | None\n    formatted: str\n\n\ndef _load_openai_input_compare_payload(raw_json: str, side: str) -> object:\n    try:\n        payload = json.loads(raw_json)\n    except json.JSONDecodeError as error:\n        raise HTTPException(\n            status_code=400,\n            detail=(\n                f\"Invalid {side} JSON: {error.msg} \"\n                f\"(line {error.lineno}, column {error.colno})\"\n            ),\n        )\n\n    try:\n        compare_openai_inputs(payload, payload)\n    except ValueError as error:\n        raise HTTPException(status_code=400, detail=f\"Invalid {side} payload: {error}\")\n\n    return payload\n\n\n@router.post(\"/openai-input-compare\", response_model=OpenAIInputCompareResponse)\nasync def compare_openai_inputs_for_evals(\n    request: OpenAIInputCompareRequest,","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/abi/screenshot-to-code/blob/d026163f586dfa8c5c10d28c36edd59a9d3b0e88/backend/routes/evals.py#L163-L199","documentation":"400 raised by _load_openai_input_compare_payload in backend/routes/evals.py:181 when json.loads fails on the left_json or right_json body field of POST /openai-input-compare. The detail includes the exact JSONDecodeError message with line and column, so the syntax error location is identified precisely.","triggerScenarios":"POST /openai-input-compare with a body where left_json or right_json is not valid JSON — trailing commas, unescaped quotes, single-quoted strings, truncated payloads from copy-paste of captured LLM request bodies.","commonSituations":"Diffing logged OpenAI request payloads that were truncated by log rotation or manually edited; pasting JSON wrapped in smart quotes from a rich-text source.","solutions":["Use the reported line/column from the detail to fix the syntax error.","Validate both strings with JSON.parse in the client before sending.","Re-export the original payload from the log file unmodified instead of hand-editing."],"exampleFix":"// before\nbody: JSON.stringify({ left_json: leftTextarea.value, right_json: rightTextarea.value })\n\n// after\nconst left = JSON.parse(leftTextarea.value); // throws early with position info\nconst right = JSON.parse(rightTextarea.value);\nbody: JSON.stringify({ left_json: leftTextarea.value, right_json: rightTextarea.value })","handlingStrategy":"validation","validationCode":"function assertJson(text: string, label: string) {\n  try { JSON.parse(text); } catch (e) {\n    throw new Error(`Invalid ${label} JSON: ${(e as Error).message}`);\n  }\n}\nassertJson(leftJson, 'left');\nassertJson(rightJson, 'right');","typeGuard":null,"tryCatchPattern":"const res = await fetch('/openai-input-compare', ...);\nif (res.status === 400) { /* detail has line/column; jump editor to it */ }","preventionTips":["Parse both sides client-side before posting.","Feed payloads from unmodified log exports, not hand-edited text."],"tags":["http-400","json","validation","fastapi"],"backgroundTag":null,"analyzedSha":"d026163f586dfa8c5c10d28c36edd59a9d3b0e88","analyzedAt":"2026-08-14T22:02:06.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}