{"record":{"id":"c68f84b6c6ec2b5a","repo":"nexu-io/open-design","slug":"json-dumps-response-error-indent-2","errorCode":null,"errorMessage":"json.dumps(response[\"error\"], indent=2)","messagePattern":"json\\.dumps\\(response\\[\"error\"\\], indent=2\\)","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/hatch-pet/scripts/generate_pet_images.py","lineNumber":119,"sourceCode":"    for image_path in image_paths:\n        fields.append((\"image[]\", (image_path.name, image_path.read_bytes(), \"image/png\")))\n    fields.extend([\n        (\"prompt\", prompt_file.read_text(encoding=\"utf-8\")),\n        (\"size\", size),\n        (\"output_format\", \"png\"),\n    ])\n    body, content_type = _multipart_body(fields)\n    req = urllib.request.Request(\n        \"https://api.openai.com/v1/images/edits\",\n        data=body,\n        headers={\"Authorization\": f\"Bearer {api_key}\", \"Content-Type\": content_type},\n        method=\"POST\",\n    )\n    with urllib.request.urlopen(req, timeout=300) as resp:\n        output_json.write_bytes(resp.read())\n    response = json.loads(output_json.read_text(encoding=\"utf-8\"))\n    if response.get(\"error\"):\n        raise SystemExit(json.dumps(response[\"error\"], indent=2))\n    return response\n\n\ndef run_image_generation(\n    *,\n    model: str,\n    prompt_file: Path,\n    output_json: Path,\n    size: str,\n    api_key: str,\n) -> dict[str, object]:\n    output_json.parent.mkdir(parents=True, exist_ok=True)\n    payload = json.dumps({\n        \"model\": model,\n        \"prompt\": prompt_file.read_text(encoding=\"utf-8\"),\n        \"size\": size,\n        \"output_format\": \"png\",\n    }).encode()","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/hatch-pet/scripts/generate_pet_images.py#L101-L137","documentation":"Raised by run_image_edit (and identically by run_image_generation) after the OpenAI image API returned a JSON body containing a non-empty 'error' field. The full error object is pretty-printed so the caller sees the API's own message, code, and details rather than a generic failure.","triggerScenarios":"urllib succeeds (HTTP 200 or the body still parsed) but response.get('error') is truthy. Typical causes: invalid API key, rate limit, content policy rejection, unsupported size/model, or malformed multipart body.","commonSituations":"OPENAI_API_KEY expired or lacking image-edit scope; hitting rate limits during a multi-job batch; a prompt or input image flagged by content moderation; passing a size the chosen model does not support.","solutions":["Read the pretty-printed error JSON: the 'code'/'message' fields identify auth, rate-limit, moderation, or validation failures.","For 401/auth errors: rotate or refresh OPENAI_API_KEY and confirm it has image permissions.","For 429 rate limits: wait and retry with fewer concurrent jobs, or reduce the --states set.","For moderation/validation errors: adjust the prompt_file or input image and re-run that specific job with --job-id."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"response = json.loads(output_json.read_text())\nerr = response.get('error')\nif err:\n    code = err.get('code') if isinstance(err, dict) else None\n    if code in ('rate_limit_exceeded','server_error','timeout'):\n        raiseTransient(err)  # retry after backoff\n    raisePermanent(err)  # auth/moderation/validation - do not retry","typeGuard":"def is_transient_api_error(err: object) -> bool:\n    if not isinstance(err, dict):\n        return False\n    code = str(err.get('code') or '').lower()\n    msg = str(err.get('message') or '').lower()\n    return 'rate' in code or 'rate' in msg or code in ('server_error','timeout','service_unavailable')","tryCatchPattern":"import json, time, urllib.error\nfor attempt in range(max_retries):\n    try:\n        resp = run_image_edit(...)\n        if resp.get('error'):\n            err = resp['error']\n            if is_transient_api_error(err) and attempt + 1 < max_retries:\n                time.sleep(2 ** attempt)\n                continue\n            raise SystemExit(json.dumps(err, indent=2))\n        break\n    except urllib.error.URLError as e:\n        if attempt + 1 < max_retries:\n            time.sleep(2 ** attempt); continue\n        raise","preventionTips":["Confirm OPENAI_API_KEY has image-edit/generation permissions before batching.","Throttle concurrent jobs to stay under rate limits.","Inspect the pretty-printed error JSON to distinguish auth, rate-limit, and moderation failures before retrying."],"tags":["network","openai-api","rate-limit","auth","pet-pipeline"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}