calesthio/OpenMontage · error · RuntimeError

Non-JSON response from Doubao API: HTTP {response.status_cod

Error message

Non-JSON response from Doubao API: HTTP {response.status_code}

What it means

Raised by the static helper _json_or_raise when response.json() raises ValueError, i.e. the Doubao API returned a body that is not valid JSON (HTML error page, empty body, XML gateway error). It fires for both the submit call and every poll iteration, and preserves the HTTP status to distinguish WAF/gateway pages from truncated responses.

Source

Thrown at tools/audio/doubao_tts.py:368

                request_id=str(uuid.uuid4()),
                return_usage=return_usage,
            )
            response = requests_module.post(self.QUERY_URL, headers=headers, json={"task_id": task_id}, timeout=(10, 60))
            query_data = self._json_or_raise(response)
            self._raise_for_doubao_error(response.status_code, query_data)
            status = query_data.get("data", {}).get("task_status")
            if status == 2:
                return query_data
            if status == 3:
                raise RuntimeError(f"Doubao task failed: {query_data.get('message', 'unknown error')}")
        raise TimeoutError(f"Doubao task did not finish within {timeout_seconds} seconds")

    @staticmethod
    def _json_or_raise(response: Any) -> dict[str, Any]:
        try:
            return response.json()
        except ValueError as exc:
            raise RuntimeError(f"Non-JSON response from Doubao API: HTTP {response.status_code}") from exc

    def _raise_for_doubao_error(self, http_status: int, payload: dict[str, Any]) -> None:
        code = payload.get("code")
        if http_status < 400 and code == 20000000:
            return
        message = payload.get("message", "unknown error")
        hint = self._diagnostic_hint(message)
        raise RuntimeError(f"HTTP {http_status}, code {code}: {message}{hint}")

    @staticmethod
    def _diagnostic_hint(message: str) -> str:
        lowered = message.lower()
        if "load grant" in lowered or "requested grant not found" in lowered:
            return " (check DOUBAO_SPEECH_API_KEY and use the new-console X-Api-Key flow)"
        if "speaker permission denied" in lowered or "access denied" in lowered:
            return " (check voice_id/DOUBAO_SPEECH_VOICE_TYPE and voice authorization)"
        if "quota exceeded" in lowered:
            return " (check quota, concurrency, or remaining character package)"

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Capture response.text (truncated) in the error to identify the gateway/proxy source
  2. Check the HTTP status: 4xx from proxy vs 5xx from service
  3. Retry with backoff — non-JSON gateway responses are usually transient
  4. If persistent, bypass proxies or verify network egress to the Volcengine domain
Defensive patterns

Strategy: retry

Try / catch

try:
    result = doubao_tool.execute(inputs)
except RuntimeError as e:
    if "Non-JSON response" in str(e):
        backoff_and_retry(max_attempts=3)  # gateway pages are transient
    else:
        raise

Prevention

When it happens

Trigger: Gateway/WAF returns an HTML 4xx/5xx page; body truncated by a proxy timeout; empty 200 response; connection reset mid-body parsed as non-JSON.

Common situations: Rate-limited by an edge proxy (HTML 429 page), Volcengine regional outage, corporate proxy injecting an error page, or auth failure returning a non-JSON body.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/78023ec89d72777a. Report an issue: GitHub.