unslothai/unsloth · error · RuntimeError

sd-server img_gen returned a non-JSON submit response: {exc}

Error message

sd-server img_gen returned a non-JSON submit response: {exc}

What it means

The submit response had a success status but resp.json() raised ValueError — the body is not valid JSON. This means whatever answered on the server port is not speaking the sd-server API: a misconfigured base_url pointing at a different service, a proxy or captive portal intercepting the request, or a corrupted response. The parser exception is chained for details.

Source

Thrown at studio/backend/core/inference/sd_cpp_server.py:474

        job_id: Optional[str] = None
        try:
            # Submit -> 202 Accepted + job id.
            try:
                resp = self._client.post(
                    f"{self.base_url}{_IMG_GEN_PATH}", json = payload, timeout = submit_timeout
                )
            except (*_TRANSPORT_ERRORS, httpx.TimeoutException) as exc:
                raise RuntimeError(self._died_message("img_gen submit", exc)) from exc
            if resp.status_code == 429:
                raise RuntimeError("sd-server job queue is full (HTTP 429).")
            if resp.status_code not in (200, 202):
                raise RuntimeError(
                    f"sd-server img_gen submit -> {resp.status_code}: {resp.text[:500]}"
                )
            try:
                job = resp.json()
            except ValueError as exc:
                raise RuntimeError(
                    f"sd-server img_gen returned a non-JSON submit response: {exc}"
                ) from exc
            if not isinstance(job, dict):
                raise RuntimeError(
                    f"sd-server img_gen returned an unexpected submit response type: {type(job)}"
                )
            job_id = job.get("id")
            if not job_id:
                raise RuntimeError(f"sd-server img_gen returned no job id: {job}")

            # Poll the job to a terminal state.
            deadline = time.monotonic() + total_timeout
            cancel_sent_at: Optional[float] = None
            while True:
                if cancel_event is not None and cancel_event.is_set():
                    if cancel_sent_at is None:
                        self.cancel(job_id)
                        cancel_sent_at = time.monotonic()

View on GitHub (pinned to 203007d190)

Solutions

  1. Verify the base URL actually points at the sd-server instance you started (fetch /v1/models and check the JSON shape).
  2. Bypass or configure proxies for the localhost server port (NO_PROXY for 127.0.0.1).
  3. If a port collision, restart the server so it reports its real port and use that URL.
Defensive patterns

Strategy: validation

Validate before calling

import httpx
r = httpx.get(f"{base_url}/v1/models", timeout=5)
assert r.status_code == 200 and r.headers.get("content-type", "").startswith("application/json"), \
    f"{base_url} is not an sd-server instance"

Try / catch

try:
    blobs = server.img_gen(payload, ...)
except RuntimeError as e:
    if "non-JSON submit response" in str(e):
        verify_base_url_points_at_sd_server()
    raise

Prevention

When it happens

Trigger: The base_url/port belongs to another HTTP server (any JSON-unaware service, an HTML error page returning 200); a reverse proxy rewriting responses; port collision where an unrelated process bound the expected port.

Common situations: Port configuration drift after server restart picks a different port; a corporate proxy returning an HTML auth page; two sd-server instances with stale URL cached client-side.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/b9b6f427c9e599f2. Report an issue: GitHub.