huggingface/open-r1 · error · PistonError

Server error. status={status}. {res_json}

Error message

Server error. status={status}. {res_json}

What it means

PistonClient.send_execute raises PistonError('Server error. status=...') when the Piston worker's /execute HTTP endpoint returns a status code other than 200. The response body (res_json) is included in the message to surface the server's own error description. It signals a server-side rejection such as bad payload, rate limiting, or gateway errors.

Source

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

        base_delay = 1.0

        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):

View on GitHub (pinned to 1416fa0cf2)

Solutions

  1. Read the status and body in the message to identify the server-side cause
  2. Verify the piston endpoint URL is reachable and points at /api/v2 (curl {endpoint}/api/v2/runtimes)
  3. Check Piston worker logs/health; restart or scale workers if 502/503/429
  4. Fix the execute payload (language, version, files) if status is 400
  5. Increase max_retries or add client-side pacing if hitting rate limits

Example fix

// before
client = PistonClient(base_endpoints=["http://piston:2000"])  # 404 on /execute
// after
client = PistonClient(base_endpoints=["http://piston:2000/api/v2"])
Defensive patterns

Strategy: retry

Validate before calling

import aiohttp
async def endpoint_ok(url):
    try:
        async with aiohttp.ClientSession() as s:
            async with s.get(f"{url.rstrip('/')}/runtimes", timeout=aiohttp.ClientTimeout(total=5)) as r:
                return r.status == 200
    except Exception:
        return False

Type guard

def is_http_ok(status, body):
    return isinstance(status, int) and status == 200 and isinstance(body, dict)

Try / catch

try:
    res = await client.send_execute(data)
except PistonError as e:
    if str(e).startswith("Server error"):
        logger.warning("Piston HTTP failure, falling back: %s", e)
        res = run_locally(data)  # or requeue
    else:
        raise

Prevention

When it happens

Trigger: POST to {endpoint}/execute returns 4xx/5xx (e.g. 400 malformed payload, 429 rate limit, 502/503 gateway down, 404 wrong endpoint URL). The client retries up to max_retries (default 5) with exponential backoff before giving up.

Common situations: Misconfigured PISTON_ENDPOINT_URL (missing /api/v2 path, wrong port), Piston worker overloaded or restarting behind a load balancer, payload with invalid fields (bad language version), reverse proxy returning HTML error pages.

Related errors


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