oraios/serena · error · ConnectionError

ProjectServer health check failed: {e}

Error message

ProjectServer health check failed: {e}

What it means

Companion to the heartbeat check in ProjectServerClient.__init__: when the server responds but with a non-2xx status (raise_for_status) or another requests error (timeout, TLS, DNS), the constructor raises ConnectionError with the underlying exception message.

Source

Thrown at src/serena/project_server.py:175

    """

    def __init__(self, host: str = "127.0.0.1", port: int = ProjectServer.PORT, timeout: int = 300) -> None:
        """
        :param host: the host address of the project server.
        :param port: the port of the project server.
        :raises ConnectionError: if the project server is not reachable.
        """
        self._base_url = f"http://{host}:{port}"
        self._timeout = timeout

        # verify that the server is running
        try:
            response = requests_lib.get(f"{self._base_url}/heartbeat", timeout=5)
            response.raise_for_status()
        except requests_lib.ConnectionError:
            raise ConnectionError(f"ProjectServer is not reachable at {self._base_url}. Make sure the server is running.")
        except requests_lib.RequestException as e:
            raise ConnectionError(f"ProjectServer health check failed: {e}")

    def query_project(self, project_name: str, tool_name: str, tool_params_json: str) -> str:
        """
        Query a project by executing a Serena tool in its context.

        The interface matches :meth:`QueryProjectTool.apply
        <serena.tools.query_project_tools.QueryProjectTool.apply>`.

        :param project_name: the name of the project to query.
        :param tool_name: the name of the tool to execute. The tool must be read-only.
        :param tool_params_json: the parameters to pass to the tool, encoded as a JSON string.
        :return: the tool's result as a string.
        """
        payload = QueryProjectRequest(
            project_name=project_name,
            tool_name=tool_name,
            tool_params_json=tool_params_json,
        ).model_dump()

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Read the wrapped exception message for the root cause (status code vs timeout)
  2. Confirm the URL scheme matches the server (http vs https) and that /heartbeat is accessible (curl it)
  3. Increase timeout or retry after checking server logs for errors during startup
  4. Bypass or configure proxies (NO_PROXY) if a corporate proxy is intercepting localhost traffic

Example fix

// before
client = ProjectServerClient("https://localhost:24282")  # server is plain http
// after
client = ProjectServerClient("http://localhost:24282")
Defensive patterns

Strategy: retry

Validate before calling

r = requests.get(f"{base_url}/heartbeat", timeout=5)
assert r.ok, f"heartbeat status {r.status_code}"

Try / catch

try:
    client = ProjectServerClient(base_url)
except ConnectionError as e:
    log.error("health check failed: %s", e)
    client = retry_with_backoff(lambda: ProjectServerClient(base_url))

Prevention

When it happens

Trigger: Server listening but /heartbeat returning 4xx/5xx (proxy interception, auth layer, misrouted path); request timeout because the server is overloaded; HTTPS base_url against a plain-HTTP server; DNS name not resolving.

Common situations: Reverse proxies or corporate proxies returning 403/502 for /heartbeat; using https:// when the server serves http://; transient network flakiness in CI causing timeouts.

Related errors


AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29). Data as JSON: /api/errors/824234ea2dd3efed. Report an issue: GitHub.