opendatalab/MinerU · error · ValueError

{payload_name} is not valid JSON

Error message

{payload_name} is not valid JSON

What it means

ValueError('{payload_name} is not valid JSON') raised by _parse_json_object_response in mineru/cli/router.py when calling response.json() on an httpx response from a (remote or local) mineru worker fails. The HTTP body was not parseable JSON — typically an HTML error page from a proxy, an empty body, a traceback in plain text, or a gateway timeout page. It means the router could not even begin to interpret the worker's reply.

Source

Thrown at mineru/cli/router.py:109

def get_int_env(name: str, default: int, minimum: int = 0) -> int:
    try:
        value = int(os.getenv(name, str(default)))
    except ValueError:
        return default
    if value < minimum:
        return default
    return value


def _parse_json_object_response(
    response: httpx.Response,
    payload_name: str,
) -> dict[str, Any]:
    try:
        payload = response.json()
    except ValueError as exc:
        raise ValueError(f"{payload_name} is not valid JSON") from exc
    if not isinstance(payload, dict):
        raise ValueError(f"{payload_name} must be a JSON object")
    return payload


def get_task_retention_seconds() -> int:
    return get_int_env(
        "MINERU_API_TASK_RETENTION_SECONDS",
        DEFAULT_TASK_RETENTION_SECONDS,
        minimum=0,
    )


def get_task_cleanup_interval_seconds() -> int:
    return get_int_env(
        "MINERU_API_TASK_CLEANUP_INTERVAL_SECONDS",
        DEFAULT_TASK_CLEANUP_INTERVAL_SECONDS,
        minimum=1,

View on GitHub (pinned to 4fe4bde114)

Solutions

  1. Inspect the raw body: log response.text (first ~500 chars) to see what was actually returned — the HTML/text usually names the real problem (proxy error, port conflict).
  2. Verify the worker URL/port: curl the health endpoint directly and confirm it returns JSON.
  3. Fix proxy behavior: raise proxy timeouts, whitelist the route, or bypass the proxy for worker traffic.
  4. If the body is empty after a crash, investigate worker logs for OOM/CUDA failures.

Example fix

# before (debugging blind)
payload = _parse_json_object_response(response, 'health payload')  # raises bare

# after (diagnose)
try:
    payload = _parse_json_object_response(response, 'health payload')
except ValueError:
    print(response.status_code, response.headers.get('content-type'))
    print(response.text[:500])  # reveals the proxy/HTML error page
    raise
Defensive patterns

Strategy: try-catch

Validate before calling

def worker_speaks_json(base_url: str) -> bool:
    r = requests.get(f'{base_url}/health', timeout=5)
    return 'json' in r.headers.get('content-type', '') and r.text.lstrip()[:1] in '{['

Type guard

import json

def looks_like_json_object(text: str) -> bool:
    try:
        return isinstance(json.loads(text), dict)
    except ValueError:
        return False

Try / catch

try:
    payload = _parse_json_object_response(response, 'health payload')
except ValueError as exc:
    logger.error('non-JSON worker reply: status=%s ct=%s body=%r',
                 response.status_code, response.headers.get('content-type'), response.text[:300])
    raise

Prevention

When it happens

Trigger: A reverse proxy (nginx/traefik) in front of the worker returning a 502/504 HTML page; the worker crashing mid-response so the body truncates; hitting a port served by a non-mineru service; auth middleware intercepting with an HTML login page; response gzipped/garbled by a misconfigured proxy.

Common situations: Docker/Kubernetes setups with ingress proxies between router and workers; worker OOM producing partial responses; wrong SERVER_URL/port in the worker list pointing at another HTTP service; TLS termination rewriting bodies.

Related errors


AI-assisted analysis of opendatalab/MinerU@4fe4bde114 (2026-08-14). Data as JSON: /api/errors/37eda44fb5f35dac. Report an issue: GitHub.