{"record":{"id":"ba461b58e411fa8f","repo":"zylon-ai/private-gpt","slug":"server-is-already-running-with-pid-existing-pid","errorCode":null,"errorMessage":"Server is already running with PID {existing_pid}","messagePattern":"Server is already running with PID (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"private_gpt/cli/commands/serve.py","lineNumber":42,"sourceCode":"    ),\n    log_level: str = typer.Option(\n        \"info\", \"--log-level\", help=\"debug | info | warn | error\"\n    ),\n    pid_file: Path | None = PID_FILE_OPTION,\n) -> None:\n    \"\"\"Start the HTTP server.\"\"\"\n    s = settings()\n    resolved_port = port if port is not None else s.server.port\n    logger.info(\n        \"Starting server with profiles=%s on port %s\", active_profiles, resolved_port\n    )\n\n    if pid_file and pid_file.exists():\n        try:\n            existing_pid = int(pid_file.read_text().strip())\n            os.kill(existing_pid, 0)\n            typer.echo(f\"Server is already running with PID {existing_pid}\", err=True)\n            raise SystemExit(1)\n        except ProcessLookupError:\n            pass  # stale PID file\n\n    if pid_file:\n        pid_file.parent.mkdir(parents=True, exist_ok=True)\n        pid_file.write_text(str(os.getpid()))\n\n    def _on_sigterm(signum: int, frame: object) -> None:\n        if pid_file and pid_file.exists():\n            pid_file.unlink(missing_ok=True)\n        raise SystemExit(0)\n\n    signal.signal(signal.SIGTERM, _on_sigterm)\n\n    try:\n        uvicorn.run(\n            \"private_gpt.main:app\",\n            host=host,","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/cli/commands/serve.py#L24-L60","documentation":"CLI error from `private-gpt serve` (private_gpt/cli/commands/serve.py). Before binding, serve checks a PID file: if it exists and os.kill(pid, 0) succeeds (a live process owns that PID), it prints 'Server is already running with PID <n>' to stderr and exits 1. A stale PID file (ProcessLookupError) is ignored and cleaned up implicitly by proceeding.","triggerScenarios":"Running `private-gpt serve` twice concurrently; a previous server still running in another terminal/tmux; the PID from the file being reused by an unrelated live process (rare PID-reuse false positive).","commonSituations":"Forgotten background server from an earlier session; CI job or supervisor restarting serve while the old instance lives; container restart where the old process survived.","solutions":["Use the existing server instead of starting another: check `curl http://localhost:<port>/health`.","Stop the old instance: `kill <existing_pid>` (the PID is printed in the message), wait for exit, then re-run serve.","If the PID belongs to an unrelated process (PID reuse), delete the PID file and start again.","In scripts, guard with a health check before invoking serve."],"exampleFix":"// before\n$ private-gpt serve\nServer is already running with PID 4242\n\n// after\n$ kill 4242 && sleep 1\n$ private-gpt serve","handlingStrategy":"validation","validationCode":"import urllib.request\n\ndef already_serving(port: int) -> bool:\n    try:\n        urllib.request.urlopen(f\"http://127.0.0.1:{port}/health\", timeout=1)\n        return True\n    except Exception:\n        return False\n\n# skip `private-gpt serve` when already_serving(port) is True","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Health-check the port before invoking serve in scripts.","Kill or reuse the printed existing PID rather than fighting the guard.","Clean stale PID files after crashes.","Run one serve per port; use separate ports for parallel instances."],"tags":["cli","server","pid-file","concurrency","exit-code"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}