{"record":{"id":"9844e29f13b0c01f","repo":"opendatalab/MinerU","slug":"local-worker-server-id-exited-before-becoming-he","errorCode":null,"errorMessage":"Local worker {server_id} exited before becoming healthy","messagePattern":"Local worker (.+?) exited before becoming healthy","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"critical","filePath":"mineru/cli/router.py","lineNumber":462,"sourceCode":"        self.process_group_id = self.process.pid\n\n        try:\n            await self.wait_until_ready(client)\n        except Exception:\n            self.stop()\n            raise\n\n    async def wait_until_ready(\n        self,\n        client: httpx.AsyncClient,\n        timeout_seconds: float = LOCAL_API_STARTUP_TIMEOUT_SECONDS,\n    ) -> None:\n        assert self.base_url is not None\n        deadline = asyncio.get_running_loop().time() + timeout_seconds\n        last_error: str | None = None\n        while asyncio.get_running_loop().time() < deadline:\n            if self.process is not None and self.process.poll() is not None:\n                raise RuntimeError(f\"Local worker {self.server_id} exited before becoming healthy\")\n            try:\n                response = await client.get(f\"{self.base_url}{HEALTH_ENDPOINT}\")\n                if response.status_code == 200:\n                    return\n                last_error = response_detail(response)\n            except httpx.HTTPError as exc:\n                last_error = str(exc)\n            await asyncio.sleep(TASK_STATUS_POLL_INTERVAL_SECONDS)\n\n        message = f\"Timed out waiting for local worker {self.server_id} to become healthy\"\n        if last_error:\n            message = f\"{message}: {last_error}\"\n        raise RuntimeError(message)\n\n    async def restart(self, client: httpx.AsyncClient) -> None:\n        self.stop()\n        await self.start(client)\n","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/opendatalab/MinerU/blob/4fe4bde114a23ee5dd637eae99b767f4669bf58c/mineru/cli/router.py#L444-L480","documentation":"RuntimeError raised in router.py by LocalWorker.wait_until_ready: the subprocess spawned for a local API worker terminated (process.poll() is not None) before its /health endpoint ever returned 200. The worker process died during startup — the most information-rich failure in the local-worker lifecycle because the subprocess's own stderr usually holds the root cause.","triggerScenarios":"Worker process crashes on import (missing dependency, incompatible torch/CUDA); model files missing or corrupted so startup aborts; port already in use causing immediate exit; Python environment mismatch (worker spawned with a different interpreter); OOM kill during model preload.","commonSituations":"Fresh installs with incomplete dependencies; GPU driver/CUDA mismatches; two services contending for the same port; container memory limits killing the worker during model load; upgrading mineru leaving stale caches.","solutions":["Reproduce the worker startup manually (the same command the router spawns) and read the traceback it prints — that is the real error.","Fix the environment: reinstall dependencies (pip install -U mineru ...), verify CUDA/driver compatibility, free the port.","Check dmesg/systemd logs for OOM kills if there is no traceback; raise memory limits or preload fewer models.","Validate the install with the mineru CLI sanity commands before running the router."],"exampleFix":"# before\nrouter start -> RuntimeError: Local worker 1 exited before becoming healthy\n\n# after (diagnose: run the worker command directly)\n# python -m mineru.cli.fast_api --host 127.0.0.1 --port 8001\n# -> ModuleNotFoundError: No module named 'magic_pdf'\n# pip install -U mineru[core] && retry","handlingStrategy":"try-catch","validationCode":"# smoke-test the worker command before letting the router spawn it\nimport subprocess, sys\nr = subprocess.run([sys.executable, '-m', 'mineru.cli.fast_api', '--help'],\n                   capture_output=True, text=True, timeout=60)\nassert r.returncode == 0, r.stderr[-2000:]","typeGuard":null,"tryCatchPattern":"try:\n    await worker.wait_until_ready(client)\nexcept RuntimeError as exc:\n    if 'exited before becoming healthy' in str(exc):\n        stderr = read_worker_log(worker)  # subprocess stderr holds the root cause\n        raise RuntimeError(f'worker startup crash: {stderr[-2000:]}') from exc\n    raise","preventionTips":["Run the worker process manually once in new environments to surface import/CUDA errors.","Verify port availability before start (ss -ltnp / docker port checks).","Keep GPU/CPU dependency extras installed consistently (mineru[core]/[full]) and pin versions."],"tags":["mineru","subprocess","startup","environment","worker-crash"],"backgroundTag":null,"analyzedSha":"4fe4bde114a23ee5dd637eae99b767f4669bf58c","analyzedAt":"2026-08-14T21:29:18.456Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}