hermes - ✅(Solved) Fix Slash-skill load failures are queued as input instead of surfacing an error [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#14713Fetched 2026-04-24 06:15:08
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×4commented ×1cross-referenced ×1

Error Message

When a slash-skill is registered but its payload fails to load, the CLI treats the failure string as a successful command payload and queues it into _pending_input instead of surfacing a user-facing error.

  • agent/skill_commands.py:311-313build_skill_invocation_message() returns a truthy error string like [Failed to load skill: plan] A runtime skill-load failure is converted into fake conversational input. The model receives an error sentinel as if it were a real user/system payload, while the CLI prints a success message (Plan mode queued...). This is a silent failure mode and can confuse both the user and the model. If a skill payload fails to load, the CLI should show an error and avoid queueing any payload. Make build_skill_invocation_message() return a structured failure result (or None plus logged error) instead of a truthy string, and update /plan plus generic skill dispatch to reject error sentinels explicitly.

Fix Action

Fix / Workaround

Minimal reproduction

cd /Users/genie/.hermes/hermes-agent
source venv/bin/activate
python - <<'PY'
from queue import Queue
from unittest.mock import patch
import cli

with patch("cli.build_plan_path", return_value="plans/test.md"), \
     patch("cli.build_skill_invocation_message", return_value="[Failed to load skill: plan]"):
    cli.HermesCLI._handle_plan_command(obj, "/plan investigate bug")
    print("queued", not obj._pending_input.empty())
    if not obj._pending_input.empty():
        print("payload", obj._pending_input.get())
PY

Observed output includes:

  • Plan mode queued via skill...
  • queued True
  • payload [Failed to load skill: plan]

Suggested investigation direction

Make build_skill_invocation_message() return a structured failure result (or None plus logged error) instead of a truthy string, and update /plan plus generic skill dispatch to reject error sentinels explicitly.

PR fix notes

PR #14752: fix: skill load failures return None instead of queued sentinel (#14713)

Description (problem / solution / changelog)

Summary

Fixes #14713.

build_skill_invocation_message() used to return a truthy string like [Failed to load skill: …] when _load_skill_payload failed. The CLI only checked truthiness, so that string was queued as normal user input while still printing success-style messaging for /plan.

Changes

  • agent/skill_commands.py: On payload load failure, log a warning and return None (docstring updated). Same contract as “unknown skill” for callers that already branch on falsy messages.
  • gateway/run.py: When a skill command resolves but the message is None, return an explicit user-facing error instead of falling through to the agent with stale/empty text.
  • tui_gateway/server.py: Return HTTP-style _err(..., 5030, …) for skill slash and /plan when the invocation message is missing (avoids falling through to “not a quick/plugin/skill command”).

Tests

  • tests/agent/test_skill_commands.py: test_returns_none_when_payload_load_fails — patches _load_skill_payload to None and asserts build_skill_invocation_message returns None.

Notes

Local pytest was not run (environment Python < 3.11); CI matrix should cover tests/agent/test_skill_commands.py.

Made with Cursor

Changed files

  • agent/skill_commands.py (modified, +10/-2)
  • gateway/run.py (modified, +6/-0)
  • tests/agent/test_skill_commands.py (modified, +9/-0)
  • tui_gateway/server.py (modified, +6/-0)

Code Example

cd /Users/genie/.hermes/hermes-agent
source venv/bin/activate
python - <<'PY'
from queue import Queue
from unittest.mock import patch
import cli

obj = object.__new__(cli.HermesCLI)
obj.session_id = "repro"
obj._pending_input = Queue()

with patch("cli.build_plan_path", return_value="plans/test.md"), \
     patch("cli.build_skill_invocation_message", return_value="[Failed to load skill: plan]"):
    cli.HermesCLI._handle_plan_command(obj, "/plan investigate bug")
    print("queued", not obj._pending_input.empty())
    if not obj._pending_input.empty():
        print("payload", obj._pending_input.get())
PY
RAW_BUFFERClick to expand / collapse

Bug Description

When a slash-skill is registered but its payload fails to load, the CLI treats the failure string as a successful command payload and queues it into _pending_input instead of surfacing a user-facing error.

Affected files / lines

  • agent/skill_commands.py:311-313build_skill_invocation_message() returns a truthy error string like [Failed to load skill: plan]
  • cli.py:5569-5579 — generic skill command path only checks truthiness before queueing
  • cli.py:5629-5646/plan path only checks if not msg, so the failure string is queued too

Why this is a bug

A runtime skill-load failure is converted into fake conversational input. The model receives an error sentinel as if it were a real user/system payload, while the CLI prints a success message (Plan mode queued...). This is a silent failure mode and can confuse both the user and the model.

Minimal reproduction

cd /Users/genie/.hermes/hermes-agent
source venv/bin/activate
python - <<'PY'
from queue import Queue
from unittest.mock import patch
import cli

obj = object.__new__(cli.HermesCLI)
obj.session_id = "repro"
obj._pending_input = Queue()

with patch("cli.build_plan_path", return_value="plans/test.md"), \
     patch("cli.build_skill_invocation_message", return_value="[Failed to load skill: plan]"):
    cli.HermesCLI._handle_plan_command(obj, "/plan investigate bug")
    print("queued", not obj._pending_input.empty())
    if not obj._pending_input.empty():
        print("payload", obj._pending_input.get())
PY

Observed output includes:

  • Plan mode queued via skill...
  • queued True
  • payload [Failed to load skill: plan]

Expected Behavior

If a skill payload fails to load, the CLI should show an error and avoid queueing any payload.

Actual Behavior

The CLI announces success and queues the failure sentinel as input.

Suggested investigation direction

Make build_skill_invocation_message() return a structured failure result (or None plus logged error) instead of a truthy string, and update /plan plus generic skill dispatch to reject error sentinels explicitly.

extent analysis

TL;DR

Modify build_skill_invocation_message() to return a structured failure result or None with a logged error, and update the /plan and generic skill dispatch paths to reject error sentinels explicitly.

Guidance

  • Update build_skill_invocation_message() in agent/skill_commands.py to return a structured failure result, such as a dictionary with an error message, or None with a logged error.
  • Modify the /plan path in cli.py to check for a specific error sentinel or a structured failure result instead of just checking truthiness.
  • Update the generic skill command path in cli.py to reject error sentinels explicitly, ensuring that only valid payloads are queued.
  • Verify the changes by running the minimal reproduction script and checking that the CLI correctly handles skill payload failures.

Example

# agent/skill_commands.py
def build_skill_invocation_message():
    # ...
    if failure:
        # Log the error and return None or a structured failure result
        logging.error("Failed to load skill: plan")
        return None
    # ...

# cli.py
def _handle_plan_command(self, command):
    # ...
    payload = build_skill_invocation_message()
    if payload is None:
        # Handle the error case explicitly
        print("Error: Failed to load skill")
    else:
        # Queue the valid payload
        self._pending_input.put(payload)

Notes

The suggested changes assume that the build_skill_invocation_message() function can be modified to return a structured failure result or None with a logged error. Additional error handling may be necessary to ensure that the CLI correctly handles all possible failure cases.

Recommendation

Apply the suggested workaround by modifying build_skill_invocation_message() and updating the /plan and generic skill dispatch paths to reject error sentinels explicitly, as this will allow the CLI to correctly handle skill payload failures

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING