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
- Verify the base URL actually points at the sd-server instance you started (fetch /v1/models and check the JSON shape).
- Bypass or configure proxies for the localhost server port (NO_PROXY for 127.0.0.1).
- 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
- Health-check /v1/models for JSON before using a configured base_url.
- Exclude localhost from HTTP proxies (NO_PROXY) in corporate environments.
- Always derive the URL from the server instance you started, never hardcode a port.
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
- failed to spawn sd-server: {self._spawn_error}
- sd-server failed to become ready. Last output: {tail[:2000]}
- sd-server is not running.
- sd-server connection lost during img_gen submit
- sd-server job queue is full (HTTP 429).
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/b9b6f427c9e599f2.
Report an issue: GitHub.