nexu-io/open-design · error · SystemExit

json.dumps(response["error"], indent=2)

Error message

json.dumps(response["error"], indent=2)

What it means

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.

Source

Thrown at skills/hatch-pet/scripts/generate_pet_images.py:119

    for image_path in image_paths:
        fields.append(("image[]", (image_path.name, image_path.read_bytes(), "image/png")))
    fields.extend([
        ("prompt", prompt_file.read_text(encoding="utf-8")),
        ("size", size),
        ("output_format", "png"),
    ])
    body, content_type = _multipart_body(fields)
    req = urllib.request.Request(
        "https://api.openai.com/v1/images/edits",
        data=body,
        headers={"Authorization": f"Bearer {api_key}", "Content-Type": content_type},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=300) as resp:
        output_json.write_bytes(resp.read())
    response = json.loads(output_json.read_text(encoding="utf-8"))
    if response.get("error"):
        raise SystemExit(json.dumps(response["error"], indent=2))
    return response


def run_image_generation(
    *,
    model: str,
    prompt_file: Path,
    output_json: Path,
    size: str,
    api_key: str,
) -> dict[str, object]:
    output_json.parent.mkdir(parents=True, exist_ok=True)
    payload = json.dumps({
        "model": model,
        "prompt": prompt_file.read_text(encoding="utf-8"),
        "size": size,
        "output_format": "png",
    }).encode()

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the pretty-printed error JSON: the 'code'/'message' fields identify auth, rate-limit, moderation, or validation failures.
  2. For 401/auth errors: rotate or refresh OPENAI_API_KEY and confirm it has image permissions.
  3. For 429 rate limits: wait and retry with fewer concurrent jobs, or reduce the --states set.
  4. For moderation/validation errors: adjust the prompt_file or input image and re-run that specific job with --job-id.
Defensive patterns

Strategy: retry

Validate before calling

response = json.loads(output_json.read_text())
err = response.get('error')
if err:
    code = err.get('code') if isinstance(err, dict) else None
    if code in ('rate_limit_exceeded','server_error','timeout'):
        raiseTransient(err)  # retry after backoff
    raisePermanent(err)  # auth/moderation/validation - do not retry

Type guard

def is_transient_api_error(err: object) -> bool:
    if not isinstance(err, dict):
        return False
    code = str(err.get('code') or '').lower()
    msg = str(err.get('message') or '').lower()
    return 'rate' in code or 'rate' in msg or code in ('server_error','timeout','service_unavailable')

Try / catch

import json, time, urllib.error
for attempt in range(max_retries):
    try:
        resp = run_image_edit(...)
        if resp.get('error'):
            err = resp['error']
            if is_transient_api_error(err) and attempt + 1 < max_retries:
                time.sleep(2 ** attempt)
                continue
            raise SystemExit(json.dumps(err, indent=2))
        break
    except urllib.error.URLError as e:
        if attempt + 1 < max_retries:
            time.sleep(2 ** attempt); continue
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/c68f84b6c6ec2b5a. Report an issue: GitHub.