opendatalab/MinerU · error · ValueError
Unsupported protocol_version=
Error message
Unsupported protocol_version={protocol_version}, expected {API_PROTOCOL_VERSION} What it means
ValueError raised in router.py by _update_server_from_health_payload: the health payload's protocol_version field does not equal the router's compiled-in API_PROTOCOL_VERSION. The router and workers speak a versioned protocol (task submission, health stats, result shapes); a mismatch means the installations are of different mineru versions and their message formats cannot be trusted to agree, so the server is marked unhealthy instead of being used.
Solutions
- Print/compare versions: check pip show mineru (or the version endpoint) on router and every worker.
- Upgrade (or pin) all components to the exact same mineru release, then restart workers.
- Rebuild all Docker images together in mixed deployments.
- If writing a custom worker, echo back the router's expected API_PROTOCOL_VERSION and implement the matching payload schema.
Example fix
# before # router: mineru 2.5.1, worker container: mineru 2.3.0 -> ValueError on health refresh # after pip install -U 'mineru==2.5.1' # on router host docker build --pull --tag mineru-worker:2.5.1 . # rebuild workers to same version
Defensive patterns
Strategy: validation
Validate before calling
import httpx
from mineru.cli.router import API_PROTOCOL_VERSION
def worker_matches_protocol(base_url: str) -> bool:
payload = httpx.get(f'{base_url}/health', timeout=5).json()
return payload.get('protocol_version') == API_PROTOCOL_VERSION Type guard
def health_payload_matches(payload: dict, expected: str) -> bool:
return isinstance(payload, dict) and payload.get('protocol_version') == expected Try / catch
try:
await router._refresh_server(server)
except ValueError as exc:
if 'protocol_version' in str(exc):
logger.error('version skew on %s: pin all mineru components to one release', server.base_url)
raise Prevention
- Install the identical mineru version on router and every worker; record it in deployment manifests.
- Rebuild all docker images together during upgrades — never partial rollouts of the worker pool.
- Add a startup check comparing protocol_version across the pool before accepting traffic.
When it happens
Trigger: Router process from mineru 1.x managing workers from mineru 1.y (or a mixed Docker image set); one side upgraded via pip while the other was left behind; a hand-written third-party 'worker' that omits or fakes protocol_version; stale worker containers after a partial rollout.
Common situations: Piecemeal upgrades across a fleet; dev machines sharing a worker pool; docker-compose setups where only one service was rebuilt.
Related errors
- must be a JSON object
- max_concurrent_requests must be a positive integer
- is not valid JSON
- Timed out waiting for local worker
- ak, sk or endpoint not found in
AI-assisted analysis of opendatalab/MinerU@4fe4bde114 (2026-08-14).
Data as JSON: /api/errors/92806c486c011ce5.
Report an issue: GitHub.
Appendix: source
Thrown at mineru/cli/router.py:649
server.local_server.stop()
async def _monitor_loop(self) -> None:
while True:
await asyncio.sleep(self.settings.worker_refresh_interval_seconds)
await self.refresh_all()
async def refresh_all(self) -> None:
for server in self.servers:
await self._refresh_server(server)
def _update_server_from_health_payload(
self,
server: WorkerState,
payload: dict[str, Any],
) -> None:
protocol_version = payload.get("protocol_version")
if protocol_version != API_PROTOCOL_VERSION:
raise ValueError(
f"Unsupported protocol_version={protocol_version}, expected {API_PROTOCOL_VERSION}"
)
server.queued_tasks = int(payload.get("queued_tasks", 0))
server.processing_tasks = int(payload.get("processing_tasks", 0))
server.completed_tasks = int(payload.get("completed_tasks", 0))
server.failed_tasks = int(payload.get("failed_tasks", 0))
server.max_concurrent_requests = int(payload.get("max_concurrent_requests", 0))
if server.max_concurrent_requests <= 0:
raise ValueError("max_concurrent_requests must be a positive integer")
server.processing_window_size = max(
MIN_HEALTHY_PROCESSING_WINDOW_SIZE,
int(
payload.get(
"processing_window_size",
MIN_HEALTHY_PROCESSING_WINDOW_SIZE,
)
),View on GitHub (pinned to 4fe4bde114)