Skip to content

Commit 6450508

Browse files
committed
fix(extensions): preserve command registry when skills fail
1 parent b68eec5 commit 6450508

2 files changed

Lines changed: 64 additions & 5 deletions

File tree

src/specify_cli/extensions.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,11 +1703,31 @@ def register_enabled_extensions_for_agent(self, agent_name: str) -> None:
17031703
if new_registered != registered_commands:
17041704
updates["registered_commands"] = new_registered
17051705

1706-
registered_skills = self._register_extension_skills(manifest, ext_dir)
1707-
if registered_skills:
1708-
existing_skills = self._valid_name_list(metadata.get("registered_skills", []))
1709-
merged_skills = list(dict.fromkeys(existing_skills + registered_skills))
1710-
updates["registered_skills"] = merged_skills
1706+
try:
1707+
registered_skills = self._register_extension_skills(manifest, ext_dir)
1708+
except Exception as skills_err:
1709+
# Skills are a companion artifact. If command registration
1710+
# already succeeded, still persist it so later cleanup can
1711+
# find those command files.
1712+
from . import _print_cli_warning
1713+
1714+
_print_cli_warning(
1715+
"register extension skills for",
1716+
"extension",
1717+
ext_id,
1718+
skills_err,
1719+
continuing=(
1720+
"Continuing with command registration for this "
1721+
"extension and the remaining extensions."
1722+
),
1723+
)
1724+
else:
1725+
if registered_skills:
1726+
existing_skills = self._valid_name_list(
1727+
metadata.get("registered_skills", [])
1728+
)
1729+
merged_skills = list(dict.fromkeys(existing_skills + registered_skills))
1730+
updates["registered_skills"] = merged_skills
17111731

17121732
if updates:
17131733
self.registry.update(ext_id, updates)

tests/test_extension_skills.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,45 @@ def flaky(self, agent_name, manifest, ext_dir, project_root, link_outputs=False)
10281028
fail_meta = manager.registry.get("aaa-fail")
10291029
assert "claude" not in fail_meta.get("registered_commands", {})
10301030

1031+
def test_skill_registration_failure_preserves_registered_commands(
1032+
self, project_dir, temp_dir, monkeypatch, capsys
1033+
):
1034+
"""Persist successful command registration even if skills fail.
1035+
1036+
If command files are written but skill generation raises, the command
1037+
registry must still be updated so later unregister/cleanup can find the
1038+
command files.
1039+
"""
1040+
_create_init_options(project_dir, ai="claude", ai_skills=False)
1041+
manager = ExtensionManager(project_dir)
1042+
manager.install_from_directory(
1043+
_create_extension_dir(temp_dir, ext_id="skill-fail"), "0.1.0",
1044+
register_commands=False,
1045+
)
1046+
1047+
def fail_skills(self, manifest, ext_dir, link_outputs=False):
1048+
raise OSError("simulated skill directory failure")
1049+
1050+
monkeypatch.setattr(
1051+
ExtensionManager, "_register_extension_skills", fail_skills
1052+
)
1053+
1054+
manager.register_enabled_extensions_for_agent("claude")
1055+
1056+
metadata = manager.registry.get("skill-fail")
1057+
assert metadata is not None
1058+
assert metadata["registered_commands"] == {
1059+
"claude": [
1060+
"speckit.skill-fail.hello",
1061+
"speckit.skill-fail.world",
1062+
]
1063+
}
1064+
assert metadata["registered_skills"] == []
1065+
1066+
captured = capsys.readouterr()
1067+
assert "register extension skills for extension 'skill-fail'" in captured.out
1068+
assert "Continuing with command registration" in captured.out
1069+
10311070
def test_existing_agent_command_path_file_is_not_detected(
10321071
self, project_dir, temp_dir
10331072
):

0 commit comments

Comments
 (0)