oraios/serena · error · ValueError

Tool '{req.tool_name}' is not read-only and cannot be execut

Error message

Tool '{req.tool_name}' is not read-only and cannot be executed via the query_project route

What it means

ProjectServer's query_project route is restricted to read-only tools. Before applying the requested tool it checks tool.is_readonly(), and mutating tools (editors, shell executors, etc.) are rejected to keep the server side-effect free for concurrent queries.

Source

Thrown at src/serena/project_server.py:133

                project.create_language_server_manager()

            with self._loaded_projects_lock:
                self._loaded_projects_by_root[key] = project
            return project

    def _query_project(self, req: QueryProjectRequest) -> str:
        """Handle a /query_project request by invoking the agent on the specified project and tool.

        The active project is process-wide state, whereas ``apply_ex`` runs the tool on the
        agent's task executor thread. Without the lock, a second request entering
        ``active_project_context`` while the first request's tool is still executing would
        redirect that tool to the wrong project (and restore the wrong project afterwards).
        """
        project = self._get_project(req.project_name)
        with self._active_project_lock, self._agent.active_project_context(project):
            tool = self._agent.get_tool_by_name(req.tool_name)
            if not tool.is_readonly():
                raise ValueError(f"Tool '{req.tool_name}' is not read-only and cannot be executed via the query_project route")
            params = json.loads(req.tool_params_json)
            return tool.apply_ex(**params)

    def run(self) -> None:
        """
        Run the server on the given host and port.
        """
        from flask import cli

        # suppress the default Flask startup banner
        # ty cannot model reassigning a third-party module's function attribute (it rejects any
        # replacement, even one with an identical signature), so the monkeypatch is suppressed here
        cli.show_server_banner = lambda *args, **kwargs: None  # ty: ignore[invalid-assignment]

        self._app.run(host=self._host, port=self._port, debug=False, use_reloader=False, threaded=True)


class ProjectServerClient:

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Use a read-only tool for query_project (find_symbol, find_referencing_symbols, etc.)
  2. Apply edits through the interactive Serena agent/session instead of the ProjectServer query route
  3. If you own the tool, ensure is_readonly() correctly reflects its behavior rather than weakening this check

Example fix

// before
server.query_project("my-repo", "replace_symbol_body", params)
// after
server.query_project("my-repo", "find_symbol", params)  # read-only only
Defensive patterns

Strategy: validation

Validate before calling

tool = agent.get_tool_by_name(tool_name)
if not tool.is_readonly():
    raise ValueError(f"{tool_name} must be run via the interactive agent, not query_project")

Type guard

def is_readonly_tool(agent, tool_name: str) -> bool:
    return agent.get_tool_by_name(tool_name).is_readonly()

Try / catch

try:
    return server.query_project(project, tool_name, params)
except ValueError as e:
    if "not read-only" in str(e):
        return run_via_interactive_agent(project, tool_name, params)
    raise

Prevention

When it happens

Trigger: Calling query_project with tool_name set to a write tool such as replace_symbol_body, insert_after_symbol, delete_symbol, or execute_shell_command; passing a tool whose readonly flag is misconfigured.

Common situations: Reusing client code that worked against the full agent toolset but now targets the read-only ProjectServer; attempting quick fixes through the query endpoint instead of the interactive agent.

Related errors


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