Improve handling of configuration file updates

* Make SerenaConfig.save private, because it is not a method that should
   ever be called on a runtime instance; it is purely an internal operation
 * Refactor the CLI init command appropriately (which used the save method)
 * Improve comments to make usage limitations clear
 * Improve the changelog entry to make error conditions more concrete
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-06-08 11:36:24 +02:00
1 parent 34e7c93f78
commit a9cf93aaa3
3 files changed
+28 -11

No files matched your search

+2 -1
View File
@@ -8,7 +8,8 @@ Status of the `main` branch. Changes prior to the next official version change w
or a `.git`, including worktree/submodule pointer files), instead of preferring an ancestor's
`.serena/project.yml` over a closer `.git`. This previously bound CLI agents (Claude Code, Codex,
Gemini) launched from inside a worktree to the parent repo, causing stale reads and misdirected edits.
- Fix: some CLI flags on start-mcp-server modified the global config (notably, language-backend).
- Fix: CLI flags on `start-mcp-server` could incorrectly be saved to the global configuration file if the
list of projects was modified (triggering a save of the configuration with transient overrides applied)
* Language Servers:
- `typescript_vts`: Add `initialization_options` setting in `ls_specific_settings.typescript_vts`.
+1 -3
View File
@@ -184,9 +184,7 @@ class TopLevelCommands(AutoRegisteringGroup):
)
def init(language_backend: Literal["LSP", "JetBrains"] = "LSP") -> None:
click.echo(f"\nSerena version: {serena_version()}\n")
serena_config = SerenaConfig.from_config_file()
serena_config.language_backend = LanguageBackend(language_backend)
serena_config.save()
serena_config = SerenaConfig.init(language_backend=LanguageBackend(language_backend))
click.echo(f"Configuration file: {serena_config.config_file_path}")
click.echo(f"Language backend: {language_backend}")
+25 -7
View File
@@ -957,7 +957,7 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
# re-save the configuration file if any migrations were performed
if num_migrations > 0:
log.info("Legacy configuration was migrated; re-saving configuration file")
instance.save()
instance._save()
return instance
@@ -985,6 +985,20 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
log.error(f"Error migrating configuration file: {e}")
return None
@classmethod
def init(cls, language_backend: LanguageBackend) -> "SerenaConfig":
"""
Supports the config initialisation CLI command, allowing the user to configure fundamental settings before
the first launch.
:param language_backend: the language backend to use
:return: the created SerenaConfig instance
"""
config = cls.from_config_file()
config.language_backend = language_backend
config._save()
return config
@cached_property
def project_paths(self) -> list[str]:
return sorted(str(project.project_root) for project in self.projects)
@@ -1035,14 +1049,14 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
def add_registered_project(self, registered_project: RegisteredProject) -> None:
"""
Adds a registered project, persisting only the updated project list (see :meth:`_persist_projects`).
Adds a registered project, persisting the updated project list
"""
self.projects.append(registered_project)
self._persist_projects()
def add_project_from_path(self, project_root: Path | str) -> "Project":
"""
Add a new project to the Serena configuration from a given path, auto-generating the project
Adds a new project to the Serena configuration from a given path, auto-generating the project
with defaults if it does not exist.
Will raise a FileExistsError if a project already exists at the path.
@@ -1087,7 +1101,7 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
def _persist_projects(self) -> None:
"""
Persist ONLY the registered-projects list, leaving every other setting at its on-disk value.
Persists ONLY the registered-projects list, leaving every other setting at its on-disk value.
Project (de)registration can happen while a session is running with transient runtime overrides applied
to this in-memory instance (e.g. ``start-mcp-server --language-backend`` / ``--log-level``). A full
@@ -1099,11 +1113,15 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
return
persisted = SerenaConfig.from_config_file()
persisted.projects = list(self.projects)
persisted.save()
persisted._save()
def save(self) -> None:
def _save(self) -> None:
"""
Saves the configuration to the file from which it was loaded (if any)
Saves the full configuration to the file from which it was loaded (if any)
NOTE: This method is private, because it is not usually safe to save a configuration instance used
at runtime, because it often contains transient overrides (e.g. specified through the CLI)
that should never be persisted back to the configuration file.
"""
if self.config_file_path is None:
return