oraios/serena · error · PluginServerError

Unexpected response from readFile: {response}

Error message

Unexpected response from readFile: {response}

What it means

JetBrainsPluginClient raises PluginServerError when the JetBrains IDE plugin's /readFile HTTP endpoint returns a JSON response lacking a 'content' key. The client treats any response without 'content' as a protocol violation or unexpected plugin state, so it refuses to return a value.

Source

Thrown at src/serena/jetbrains/jetbrains_plugin_client.py:744

        :param language: optional language to filter inspections by (e.g. "Java", "Python")
        :param group_path_contains: optional substring to filter inspection group paths
        """
        self._require_version_at_least(2023, 2, 14)
        request_data: dict[str, Any] = {}
        if language is not None:
            request_data["language"] = language
        if group_path_contains is not None:
            request_data["groupPathContains"] = group_path_contains
        return cast(jb.ListInspectionsResponse, self._make_request("POST", "/listInspections", request_data))

    def read_file(self, relative_path: str) -> str:
        self._require_version_at_least(2023, 3, 3)
        request_data = {
            "relativePath": relative_path,
        }
        response = self._make_request("POST", "/readFile", request_data)
        if "content" not in response:
            raise PluginServerError(f"Unexpected response from readFile: {response}")
        return response["content"]

    def close(self) -> None:
        self._session.close()

    def __enter__(self) -> Self:
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Verify the relative_path exists inside the JetBrains project root before calling read_file
  2. Catch PluginServerError and fall back to local filesystem read
  3. Update the JetBrains plugin and serena to matching versions so the /readFile response schema matches
  4. Log the full response payload to see the error detail the plugin returned

Example fix

// before
content = client.read_file("src/missing.py")
// after
from pathlib import Path
p = project_root / "src/missing.py"
if not p.is_file():
    raise FileNotFoundError(f"{p} does not exist")
try:
    content = client.read_file("src/missing.py")
except PluginServerError as e:
    content = p.read_text()
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path
p = Path(project_root) / relative_path
if not p.is_file():
    raise FileNotFoundError(relative_path)

Type guard

def has_content(resp: dict) -> bool:
    return isinstance(resp, dict) and isinstance(resp.get("content"), str)

Try / catch

try:
    content = client.read_file(relative_path)
except PluginServerError as e:
    logger.warning("readFile failed: %s", e)
    content = (project_root / relative_path).read_text()

Prevention

When it happens

Trigger: Calling read_file(relative_path=...) when the plugin returns an error payload (e.g. {'error': 'file not found'}), an empty object, or an unexpected schema instead of {'content': ...}. Typically the file does not exist, is outside the project root, or the plugin version's response shape differs.

Common situations: Requesting a deleted or renamed file; path outside the opened JetBrains project; plugin version mismatch changing the response format; the IDE returning an error JSON for unreadable/binary files.

Related errors


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