fix(plugins): a linked plugins/<name> slot reads "already installed", not a bad manifest

A memory provider's own installer (mnemosyne-hermes install, its README's
manual `ln -s`) leaves ~/.hermes/plugins/<name> as a symlink into a venv. The
catalog install of the same name then failed in _sanitize_plugin_name with
"Invalid plugin name 'mnemosyne': resolves outside the plugins directory" and
was recorded as failure_class=manifest_invalid, though the catalog manifest is
valid (hermes plugins validate passes it). Name the link and its target, say how
to free the slot, and record already_installed.
This commit is contained in:
Teknium
2026-10-09 02:12:00 -07:00
parent 6a2909c6b1
commit d2639c0dc8
2 changed files with 27 additions and 0 deletions

View File

@@ -385,6 +385,15 @@ def _install_plugin_core(
manifest = _read_manifest_for_install(tmp_target)
plugin_name = manifest.get("name") or (
subdir.rstrip("/").rsplit("/", 1)[-1] if subdir else _pc()._repo_name_from_url(git_url))
link = plugins_dir / str(plugin_name)
from pm.filesystem import is_junction
if "/" not in str(plugin_name) and (link.is_symlink() or is_junction(link)):
# A provider's own installer (e.g. `mnemosyne-hermes install`) links its package here; the
# name is fine, the slot is taken. Say so instead of blaming the manifest.
raise _pc().PluginOperationError(
f"Plugin '{plugin_name}' is already installed outside the catalog: {link} is a link to "
f"{os.path.realpath(link)}. Delete that link to install the catalog version.",
failure_class="already_installed")
try:
target = _pc()._sanitize_plugin_name(plugin_name, plugins_dir)
except ValueError as e:

View File

@@ -85,3 +85,21 @@ def test_manifest_version_above_shared_support_is_refused_cleanly(
assert not (home / "plugins" / "demo").exists()
assert not (home / "plugins" / ".install-metadata.json").exists()
def test_a_linked_plugin_slot_is_already_installed_not_a_bad_manifest(monkeypatch, tmp_path):
"""A provider's own installer can leave ``plugins/<name>`` as a link into a venv (mnemosyne-hermes
did). The catalog install must say the slot is taken, not record ``manifest_invalid``."""
from hermes_cli import plugins_cmd
monkeypatch.setattr(plugins_cmd, "_scan_on_install_enabled", lambda: False)
repo = _plugin_repo(tmp_path, {"name": "demo", "version": "1.0.0"})
home = tmp_path / "home"
monkeypatch.setenv("HERMES_HOME", str(home))
(home / "plugins").mkdir(parents=True)
(tmp_path / "elsewhere").mkdir()
(home / "plugins" / "demo").symlink_to(tmp_path / "elsewhere", target_is_directory=True)
with pytest.raises(plugins_cmd.PluginOperationError, match="already installed outside the catalog") as exc:
plugins_cmd._install_plugin_core(repo.as_uri(), force=False)
assert exc.value.failure_class == "already_installed"