huggingface/open-r1 · error · PistonError

Empty response. status={status}

Error message

Empty response. status={status}

What it means

PistonClient.send_execute raises PistonError('Empty response. status=...') when the HTTP status is 200 but the JSON body parses to None. This indicates a proxy or worker that returned an empty 200 body instead of a valid Piston execution result. It is treated as a retriable failure.

Source

Thrown at src/open_r1/utils/competitive_programming/piston_client.py:162

        status = None
        endpoint = None

        for attempt in range(max_retries + 1):
            try:
                endpoint = await self._wait_for_endpoint()
                if attempt > 0:
                    await asyncio.sleep(1)
                async with self.session.post(
                    f"{endpoint.rstrip('/')}/execute", json=data, headers={"Content-Type": "application/json"}
                ) as response:
                    status = response.status
                    res_json = await response.json(content_type=None)

                    if status != 200:
                        raise PistonError(f"Server error. status={status}. {res_json}")
                    if res_json is None:
                        raise PistonError(f"Empty response. status={status}")
                    # piston overloaded
                    if "run" in res_json and "Resource temporarily unavailable" in res_json["run"].get("stderr", ""):
                        raise PistonError(f"Piston overloaded: {res_json['run']['stderr']}")
                    return res_json

            except (PistonError, asyncio.TimeoutError, aiohttp.ClientConnectionError, RuntimeError) as e:
                # Only retry if we haven't reached max retries yet
                if attempt < max_retries:
                    # Calculate backoff with jitter
                    delay = min(base_delay * (2**attempt), 10)  # Exponential backoff, capped at 10 seconds
                    jitter = delay * 0.2 * (2 * asyncio.get_event_loop().time() % 1 - 0.5)  # Add ±10% jitter
                    retry_delay = delay + jitter
                    print(f"Retrying in {retry_delay:.2f} seconds [{self.endpoint_ids[endpoint]}] {endpoint} - {e}")

                    # special case: worker died
                    if isinstance(e, aiohttp.ClientConnectionError) and "Connect call failed" in str(e):
                        await self._check_failed_endpoint(endpoint)
                    else:

View on GitHub (pinned to 1416fa0cf2)

Solutions

  1. Retry the request; the client already retries with backoff, so a persistent message means the endpoint is broken
  2. curl the /execute endpoint and inspect the raw response body
  3. Remove or fix the misbehaving proxy in front of Piston
  4. Point base_endpoints at the Piston worker directly to isolate the proxy
  5. Drop the unhealthy endpoint from the pool via the client's health checking

Example fix

// before
curl -s -X POST http://proxy.example.com/api/v2/execute -d '...'  # empty body
// after
curl -s -X POST http://piston-worker:2000/api/v2/execute -d '...'  # returns JSON result
Defensive patterns

Strategy: try-catch

Validate before calling

import aiohttp
async def returns_body(url, payload):
    async with aiohttp.ClientSession() as s:
        async with s.post(f"{url.rstrip('/')}/execute", json=payload) as r:
            body = await r.json(content_type=None)
            return r.status == 200 and body is not None

Type guard

def is_valid_piston_result(res_json):
    return isinstance(res_json, dict) and "run" in res_json

Try / catch

try:
    res = await client.send_execute(data)
except PistonError as e:
    if str(e).startswith("Empty response"):
        res = await client.send_execute(data)  # retry, possibly on a different endpoint
    else:
        raise

Prevention

When it happens

Trigger: response.json(content_type=None) returns None despite status==200, typically when the endpoint returns an empty body, a 204-like response, or a proxy stripping the body while keeping 200.

Common situations: Misconfigured reverse proxy / ingress that swallows response bodies, a health-check page or wrong service listening on the port, flaky network equipment truncating responses.

Related errors


AI-assisted analysis of huggingface/open-r1@1416fa0cf2 (2026-08-30). Data as JSON: /api/errors/d0073ce8557d58e7. Report an issue: GitHub.