From 55f3253ab789f2bf1b66313cb74070fd3dfcd983 Mon Sep 17 00:00:00 2001 From: Asher Haig Date: Sat, 21 Mar 2026 21:37:16 -0400 Subject: [PATCH 1/6] Fix copy-paste error messages in log strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. agent.py: f-string had literal 'f' before variable, producing "Find the current log file here: f/Users/..." instead of the path. 2. ls.py: delete_text_between_positions logged "insert_text_at_position called before Language Server started" — wrong method name. --- src/serena/agent.py | 2 +- src/solidlsp/ls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 671d2eb2..93f40c65 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -483,7 +483,7 @@ class SerenaAgent: else: log_path = SerenaPaths().last_returned_log_file_path if log_path is not None: - return f"Find the current log file here: f{log_path}" + return f"Find the current log file here: {log_path}" else: return "Unfortunately, logs are not available. We recommend enabling the web dashboard/logging in general." diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 881f070d..e37a1abc 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -817,7 +817,7 @@ class SolidLanguageServer(ABC): Delete text between the given start and end positions in the given file and return the deleted text. """ if not self.server_started: - log.error("insert_text_at_position called before Language Server started") + log.error("delete_text_between_positions called before Language Server started") raise SolidLSPException("Language Server not started") absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) From 48025c7ddfed17b0ac69f071b4726d20ae8028bb Mon Sep 17 00:00:00 2001 From: asher Date: Fri, 20 Mar 2026 19:44:13 -0400 Subject: [PATCH 2/6] Fix agent shutdown lifecycle: no-op shutdown, orphaned LS processes, missing MCP cleanup Three related bugs preventing proper cleanup of language server processes: 1. SerenaAgent.shutdown() checked `hasattr(self, "_is_initialized")` but that attribute was never set, making shutdown always return early (no-op). Replace with `hasattr(self, "_active_project")` which is set early in __init__ and correctly guards against __del__ on partial construction. 2. _activate_project() replaced self._active_project without shutting down the previous project first, orphaning its language server processes. Now calls old_project.shutdown() before switching. 3. MCP server_lifespan() logged "shutting down" but never called agent.shutdown(), so language servers were orphaned on MCP server exit. Now calls agent.shutdown() in a try/finally block. --- src/serena/agent.py | 8 +++++++- src/serena/mcp.py | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 671d2eb2..b714f679 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -699,6 +699,11 @@ class SerenaAgent: f"(2) Configure one MCP server per backend in your client." ) + # shut down the previously active project to release its language server processes + if self._active_project is not None: + log.info(f"Shutting down previously active project '{self._active_project.project_name}' before switching") + self._active_project.shutdown() + self._active_project = project project.set_agent(self) @@ -845,7 +850,8 @@ class SerenaAgent: """ Shuts down the agent, freeing resources and stopping background tasks. """ - if not hasattr(self, "_is_initialized"): + # guard against __del__ being called on a partially constructed instance + if not hasattr(self, "_active_project"): return log.info("SerenaAgent is shutting down ...") if self._active_project is not None: diff --git a/src/serena/mcp.py b/src/serena/mcp.py index 83b8278b..c83dadc7 100644 --- a/src/serena/mcp.py +++ b/src/serena/mcp.py @@ -341,8 +341,12 @@ class SerenaMCPFactory: openai_tool_compatible = self.context.name in ["chatgpt", "codex", "oaicompat-agent"] self._set_mcp_tools(mcp_server, openai_tool_compatible=openai_tool_compatible) log.info("MCP server lifetime setup complete") - yield - log.info("MCP server shutting down") + try: + yield + finally: + log.info("MCP server shutting down") + if self.agent is not None: + self.agent.shutdown() def _get_initial_instructions(self) -> str: assert self.agent is not None From 57e720bc79147c22480e528b643d75d7e6226390 Mon Sep 17 00:00:00 2001 From: Asher Haig Date: Sat, 21 Mar 2026 21:34:58 -0400 Subject: [PATCH 3/6] Fix copy-paste bugs in language server init params 1. clangd + ccls: workspace folder name was literal "$name" placeholder instead of os.path.basename(repository_absolute_path). 2. Erlang: rootUri used f"file://{path}" instead of pathlib.Path(...).as_uri(), producing malformed URIs on Windows. 3. F#: symbolKind valueSet used range(1, 26) (1-25) instead of range(1, 27) (1-26), missing TypeParameter. --- src/solidlsp/language_servers/ccls_language_server.py | 2 +- src/solidlsp/language_servers/clangd_language_server.py | 2 +- src/solidlsp/language_servers/erlang_language_server.py | 3 ++- src/solidlsp/language_servers/fsharp_language_server.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/solidlsp/language_servers/ccls_language_server.py b/src/solidlsp/language_servers/ccls_language_server.py index ade8aaf4..b9897172 100644 --- a/src/solidlsp/language_servers/ccls_language_server.py +++ b/src/solidlsp/language_servers/ccls_language_server.py @@ -118,7 +118,7 @@ class CCLS(SolidLanguageServer): "workspaceFolders": [ { "uri": root_uri, - "name": "$name", + "name": os.path.basename(repository_absolute_path), } ], # ccls supports initializationOptions but none are required for basic functionality diff --git a/src/solidlsp/language_servers/clangd_language_server.py b/src/solidlsp/language_servers/clangd_language_server.py index f0929f79..37aa6d70 100644 --- a/src/solidlsp/language_servers/clangd_language_server.py +++ b/src/solidlsp/language_servers/clangd_language_server.py @@ -228,7 +228,7 @@ class ClangdLanguageServer(SolidLanguageServer): "workspaceFolders": [ { "uri": root_uri, - "name": "$name", + "name": os.path.basename(repository_absolute_path), } ], } diff --git a/src/solidlsp/language_servers/erlang_language_server.py b/src/solidlsp/language_servers/erlang_language_server.py index a36cb799..ebf9008c 100644 --- a/src/solidlsp/language_servers/erlang_language_server.py +++ b/src/solidlsp/language_servers/erlang_language_server.py @@ -2,6 +2,7 @@ import logging import os +import pathlib import shutil import subprocess import threading @@ -134,7 +135,7 @@ class ErlangLanguageServer(SolidLanguageServer): initialize_params = { "processId": None, "rootPath": self.repository_root_path, - "rootUri": f"file://{self.repository_root_path}", + "rootUri": pathlib.Path(self.repository_root_path).as_uri(), "capabilities": { "textDocument": { "synchronization": {"didSave": True}, diff --git a/src/solidlsp/language_servers/fsharp_language_server.py b/src/solidlsp/language_servers/fsharp_language_server.py index 9d12cf5d..66e472b5 100644 --- a/src/solidlsp/language_servers/fsharp_language_server.py +++ b/src/solidlsp/language_servers/fsharp_language_server.py @@ -166,7 +166,7 @@ class FSharpLanguageServer(SolidLanguageServer): "documentHighlight": {"dynamicRegistration": True}, "documentSymbol": { "dynamicRegistration": True, - "symbolKind": {"valueSet": list(range(1, 26))}, # All SymbolKind values + "symbolKind": {"valueSet": list(range(1, 27))}, # All SymbolKind values (1-26) "hierarchicalDocumentSymbolSupport": True, }, "codeAction": { From 239f480cbb7db0ff1b1952ba2c33256b01f096d8 Mon Sep 17 00:00:00 2001 From: Asher Haig Date: Sat, 21 Mar 2026 21:35:25 -0400 Subject: [PATCH 4/6] Use shlex.quote for shell argument escaping on POSIX quote_arg() only wrapped arguments containing spaces in double quotes. Shell metacharacters ($, backtick, ;, |, &, etc.) were left unescaped. On POSIX, use shlex.quote() which single-quotes to prevent all shell interpretation. Retain original Windows behavior since cmd.exe does not interpret single-quoted strings. --- src/solidlsp/util/subprocess_util.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/solidlsp/util/subprocess_util.py b/src/solidlsp/util/subprocess_util.py index c0735f0c..175ae1e9 100644 --- a/src/solidlsp/util/subprocess_util.py +++ b/src/solidlsp/util/subprocess_util.py @@ -1,4 +1,5 @@ import platform +import shlex import subprocess @@ -15,8 +16,14 @@ def subprocess_kwargs() -> dict: def quote_arg(arg: str) -> str: """ - Adds quotes around an argument if it contains spaces. + Quotes a shell argument to prevent interpretation of metacharacters. + + Uses :func:`shlex.quote` on POSIX systems for proper escaping of all + shell-special characters. On Windows, wraps arguments containing spaces + in double quotes (Windows shell does not interpret single-quoted strings). """ - if " " not in arg: - return arg - return f'"{arg}"' + if platform.system() == "Windows": + if " " not in arg: + return arg + return f'"{arg}"' + return shlex.quote(arg) From 7978d42c4b9d7837caadc131b197714da6995352 Mon Sep 17 00:00:00 2001 From: Asher Haig Date: Sat, 21 Mar 2026 22:06:36 -0400 Subject: [PATCH 5/6] Redirect root URL to /dashboard/ Navigating to localhost:24282 returned a 404 because no route handled the root path. Added a redirect from / to /dashboard/. Fixes #1194. --- src/serena/dashboard.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index 3e372de8..a4963a01 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -5,7 +5,7 @@ from collections.abc import Callable from pathlib import Path from typing import TYPE_CHECKING, Any, Self -from flask import Flask, Response, request, send_from_directory +from flask import Flask, Response, redirect, request, send_from_directory from pydantic import BaseModel from sensai.util import logging @@ -148,6 +148,10 @@ class SerenaDashboardAPI: return self._memory_log_handler def _setup_routes(self) -> None: + @self._app.route("/") + def redirect_to_dashboard() -> Response: + return redirect("/dashboard/") # type: ignore[return-value] + # Static files @self._app.route("/dashboard/") def serve_dashboard(filename: str) -> Response: From 958b4ffd95a6a667a966cff185c6b324256aa9df Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Mon, 23 Mar 2026 11:24:34 +0100 Subject: [PATCH 6/6] Disabled F# tests in CI, mentioned bugs in readme --- README.md | 4 ++-- test/solidlsp/fsharp/test_fsharp_basic.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5fc6a9db..d4fa11ed 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,8 @@ Serena incorporates a powerful abstraction layer for the integration of language that implement the language server protocol (LSP). The underlying language servers are typically open-source projects (like Serena) or at least freely available for use. -With Serena's LSP library, we provide **support for over 30 programming languages**, including -AL, Ansible, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, GLSL, Go, Groovy (partial support), Haskell, HLSL, Java, Javascript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig. +With Serena's LSP library, we provide **support for over 40 programming languages**, including +AL, Ansible, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, F# (currently with some bugs), GLSL, Go, Groovy (partial support), Haskell, HLSL, Java, Javascript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig. > [!IMPORTANT] > Some language servers require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details. diff --git a/test/solidlsp/fsharp/test_fsharp_basic.py b/test/solidlsp/fsharp/test_fsharp_basic.py index 47baae7a..8e6ff221 100644 --- a/test/solidlsp/fsharp/test_fsharp_basic.py +++ b/test/solidlsp/fsharp/test_fsharp_basic.py @@ -10,7 +10,9 @@ from solidlsp.ls_utils import SymbolUtils from test.conftest import is_ci +# Currently, most F# tests fail, there seems to be a regression or instability. @pytest.mark.fsharp +@pytest.mark.skipif(is_ci, reason="F# language server is currently unreliable") class TestFSharpLanguageServer: @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_find_symbol(self, language_server: SolidLanguageServer) -> None: