oraios/serena · critical · ConnectionError

ProjectServer is not reachable at {self._base_url}. Make sur

Error message

ProjectServer is not reachable at {self._base_url}. Make sure the server is running.

What it means

ProjectServerClient's constructor performs a /heartbeat GET with a 5s timeout to verify the server is up. A requests.ConnectionError means nothing is listening at the configured base URL, so construction is aborted with a ConnectionError.

Source

Thrown at src/serena/project_server.py:173

    by sending a heartbeat request. If the server is not running, a
    :class:`ConnectionError` is raised.
    """

    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,

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Start the ProjectServer process and wait for it to log that it is listening before creating the client
  2. Verify the base_url host/port match the server's actual bind address (use 0.0.0.0/actual container IP in Docker)
  3. Check that nothing (firewall, VPN, port conflict) blocks the port and re-run

Example fix

// before
client = ProjectServerClient("http://localhost:24282")  # server not started
// after
subprocess.Popen(["serena-project-server", "--port", "24282"])
wait_for_heartbeat("http://localhost:24282/heartbeat")
client = ProjectServerClient("http://localhost:24282")
Defensive patterns

Strategy: retry

Validate before calling

import requests
def server_up(url: str) -> bool:
    try:
        return requests.get(f"{url}/heartbeat", timeout=5).ok
    except requests.ConnectionError:
        return False

Try / catch

for attempt in range(5):
    try:
        return ProjectServerClient(base_url)
    except ConnectionError:
        time.sleep(2 ** attempt)  # then start server if still down
raise RuntimeError("ProjectServer never became reachable")

Prevention

When it happens

Trigger: Instantiating the client before the ProjectServer process was started; wrong host/port in the base URL; server crashed or firewall blocks the port; server still starting up (slow boot exceeding implicit connect).

Common situations: Forgetting to run the ProjectServer entrypoint in scripts/CI; Docker containers where the server binds to localhost inside the container; port collisions changed the actual listening port.

Related errors


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