Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15335,8 +15335,19 @@ def main(
task_id=cli.session_id,
)
if missing_skills:
# Soft-fail: a force-loaded skill that can't be resolved is a
# degraded run, not an unusable invocation. Warn and continue
# starting the agent WITHOUT it rather than exiting 1 — an agent
# that starts can still do the work or block intelligently, whereas
# an exit-1 just thrashes the dispatcher into burning its whole
# retry budget on the identical wall (see kanban t_d85833c1).
missing_display = ", ".join(missing_skills)
raise ValueError(f"Unknown skill(s): {missing_display}")
print(
f"\033[33m⚠ Skipping unknown force-loaded skill(s): "
f"{missing_display} — continuing without them.\033[0m",
file=sys.stderr,
flush=True,
)
if skills_prompt:
cli.system_prompt = "\n\n".join(
part for part in (cli.system_prompt, skills_prompt) if part
Expand Down
26 changes: 23 additions & 3 deletions tests/cli/test_cli_preloaded_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,39 @@ def fake_cli(**kwargs):
assert cli_obj.preloaded_skills == ["hermes-agent-dev", "github-auth"]


def test_main_raises_for_unknown_preloaded_skill(monkeypatch):
def test_main_warns_for_unknown_preloaded_skill_does_not_raise(monkeypatch, capsys):
"""A missing force-loaded skill must NOT exit-1/raise — it degrades the run.

Soft-fail: warn to stderr and keep starting the agent without the skill, so
a dispatched worker can still do the work or block intelligently rather than
thrashing the dispatcher with an identical exit-1 on every retry (kanban
t_d85833c1 / observed t_b04f835d ads-optimizer nano-banana-pro).
"""
import cli as cli_mod

monkeypatch.setattr(cli_mod, "HermesCLI", lambda **kwargs: _DummyCLI(**kwargs))
created = {}

def fake_cli(**kwargs):
created["cli"] = _DummyCLI(**kwargs)
return created["cli"]

monkeypatch.setattr(cli_mod, "HermesCLI", fake_cli)
monkeypatch.setattr(
cli_mod,
"build_preloaded_skills_prompt",
lambda skills, task_id=None: ("", [], ["missing-skill"]),
)

with pytest.raises(ValueError, match=r"Unknown skill\(s\): missing-skill"):
# list_tools=True short-circuits before the agent actually runs (SystemExit
# from show_tools path), proving we got PAST the skill resolution without
# raising ValueError.
with pytest.raises(SystemExit):
cli_mod.main(skills="missing-skill", list_tools=True)

err = capsys.readouterr().err
assert "missing-skill" in err
assert "Skipping unknown force-loaded skill" in err


def test_show_banner_does_not_print_skills():
"""show_banner() no longer prints the activated skills line — it moved to run()."""
Expand Down
Loading