hermes - ✅(Solved) Fix plugins install crashes when plugin.yaml is valid YAML but not a mapping [4 pull requests, 1 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#14066Fetched 2026-04-23 07:46:57
View on GitHub
Comments
0
Participants
1
Timeline
8
Reactions
0
Participants
Timeline (top)
cross-referenced ×4labeled ×4

hermes plugins install crashes with AttributeError when a cloned plugin has a syntactically valid plugin.yaml that is not a mapping (for example a YAML list).

Error Message

AttributeError: 'list' object has no attribute 'get'

Root Cause

hermes plugins install crashes with AttributeError when a cloned plugin has a syntactically valid plugin.yaml that is not a mapping (for example a YAML list).

Fix Action

Fix / Workaround

Minimal reproduction

source venv/bin/activate
python - <<'PY'
from pathlib import Path
from unittest.mock import patch, MagicMock
import tempfile
import hermes_cli.plugins_cmd as pc

with tempfile.TemporaryDirectory() as tmp:
    plugins_dir = Path(tmp) / 'plugins'
    plugins_dir.mkdir()
    with patch('hermes_cli.plugins_cmd._plugins_dir', return_value=plugins_dir), \
         patch('hermes_cli.plugins_cmd.subprocess.run', return_value=MagicMock(returncode=0, stdout='', stderr='')), \
         patch('hermes_cli.plugins_cmd._read_manifest', return_value=['not', 'a', 'dict']), \
         patch('hermes_cli.plugins_cmd._display_after_install'):
        pc.cmd_install('owner/repo')
PY

PR fix notes

PR #14077: fix(plugins): reject non-mapping plugin manifests

Description (problem / solution / changelog)

Summary

  • validate that plugin.yaml parses to a mapping before returning it from _read_manifest()
  • fall back to {} for valid-but-non-mapping YAML so install continues down the repo-name path instead of crashing
  • add a regression test covering list-style manifests

Root cause

_read_manifest() returned yaml.safe_load(...) verbatim. When plugin.yaml was valid YAML with a non-dict top-level value, later install code called .get(...) on a list/string/number and raised AttributeError.

Fix

Coerce _read_manifest() back to its documented contract: return a dict or {}. Non-mapping manifests now log a warning and are treated as invalid metadata.

Regression coverage

  • adds TestReadManifest::test_valid_yaml_with_non_mapping_root_returns_empty_and_logs
  • locks the valid-YAML/non-mapping path that previously leaked a list out of _read_manifest()

Testing

  • scripts/run_tests.sh tests/hermes_cli/test_plugins_cmd.py::TestReadManifest::test_valid_yaml_with_non_mapping_root_returns_empty_and_logs
  • scripts/run_tests.sh tests/hermes_cli/test_plugins_cmd.py

Closes #14066

Changed files

  • hermes_cli/plugins_cmd.py (modified, +9/-1)
  • tests/hermes_cli/test_plugins_cmd.py (modified, +7/-0)

PR #14086: fix(plugins): tolerate non-mapping plugin manifests

Description (problem / solution / changelog)

Summary

  • treat valid-but-non-mapping plugin.yaml manifests as empty manifests in plugins_cmd
  • log a clear warning instead of propagating AttributeError during install
  • add regression coverage for manifest normalization and repo-name fallback

Closes #14066.

Testing

  • pytest -o addopts= tests/hermes_cli/test_plugins_cmd.py

Changed files

  • hermes_cli/plugins_cmd.py (modified, +10/-1)
  • tests/hermes_cli/test_plugins_cmd.py (modified, +39/-0)

PR #14122: fix(plugins): treat non-mapping plugin.yaml as invalid instead of crashing

Description (problem / solution / changelog)

Summary

Closes #14066

hermes plugins install (and plugin discovery at runtime) crashed with AttributeError: 'list' object has no attribute 'get' whenever a plugin's plugin.yaml was syntactically valid YAML but parsed to something other than a mapping — for example a top-level list. The three manifest-reading sites all did yaml.safe_load(...) or {} and then reached for .get(...) straight away, so existing exception handling (which only catches parse errors) didn't protect them.

What changed

Each site now checks isinstance(..., dict) before indexing:

  • _read_manifest (plugins_cmd.py) — coerce non-mapping YAML to {} and log a warning.
  • _discover_all_plugins (plugins_cmd.py) — same coercion on its inline yaml.safe_load path.
  • PluginRegistry._parse_manifest (plugins.py) — return None (skip the plugin) instead of calling .get() on a list/scalar.

The coercion/skip choice mirrors how each caller already handles missing or malformed manifests.

Test plan

  • New TestReadManifest::test_non_mapping_yaml_returns_empty_and_logs and test_scalar_yaml_returns_empty.
  • New TestPluginDiscovery::test_discover_skips_non_mapping_manifest — reproduces the crash scenario in the runtime discovery path.
  • Existing manifest/discovery tests still pass (tests/hermes_cli/test_plugins_cmd.py::TestReadManifest, tests/hermes_cli/test_plugins.py::TestPluginDiscovery).

AI-assisted contribution.

Changed files

  • hermes_cli/plugins.py (modified, +7/-0)
  • hermes_cli/plugins_cmd.py (modified, +11/-1)
  • tests/hermes_cli/test_plugins.py (modified, +23/-0)
  • tests/hermes_cli/test_plugins_cmd.py (modified, +16/-0)

PR #14147: fix(plugins): ignore non-mapping manifests

Description (problem / solution / changelog)

Summary

  • treat valid YAML manifests with a non-mapping top level as invalid plugin manifests instead of crashing on
  • harden both plugin install/list helpers and PluginManager directory scanning against non-dict manifest payloads
  • add focused regressions for install fallback and discovery skipping

Testing

  • pytest -o addopts="" tests/hermes_cli/test_plugins_cmd.py -k "non_mapping_manifest_falls_back_to_repo_name"
  • pytest -o addopts="" tests/hermes_cli/test_plugins.py -k "skips_non_mapping_manifest"
  • pytest -o addopts="" tests/hermes_cli/test_plugins_cmd.py
  • pytest -o addopts="" tests/hermes_cli/test_plugins.py

Fixes #14066

Changed files

  • hermes_cli/plugins.py (modified, +6/-0)
  • hermes_cli/plugins_cmd.py (modified, +16/-1)
  • tests/hermes_cli/test_plugins.py (modified, +16/-0)
  • tests/hermes_cli/test_plugins_cmd.py (modified, +32/-0)

Code Example

source venv/bin/activate
python - <<'PY'
from pathlib import Path
from unittest.mock import patch, MagicMock
import tempfile
import hermes_cli.plugins_cmd as pc

with tempfile.TemporaryDirectory() as tmp:
    plugins_dir = Path(tmp) / 'plugins'
    plugins_dir.mkdir()
    with patch('hermes_cli.plugins_cmd._plugins_dir', return_value=plugins_dir), \
         patch('hermes_cli.plugins_cmd.subprocess.run', return_value=MagicMock(returncode=0, stdout='', stderr='')), \
         patch('hermes_cli.plugins_cmd._read_manifest', return_value=['not', 'a', 'dict']), \
         patch('hermes_cli.plugins_cmd._display_after_install'):
        pc.cmd_install('owner/repo')
PY

---

AttributeError: 'list' object has no attribute 'get'
RAW_BUFFERClick to expand / collapse

Summary

hermes plugins install crashes with AttributeError when a cloned plugin has a syntactically valid plugin.yaml that is not a mapping (for example a YAML list).

Affected files

  • hermes_cli/plugins_cmd.py:115-127
  • hermes_cli/plugins_cmd.py:331-344
  • hermes_cli/plugins.py:344-360
  • tests/hermes_cli/test_plugins_cmd.py:185-209

Why this is a bug

_read_manifest() promises to return a dict or {}, but it currently returns yaml.safe_load(...) verbatim. If plugin.yaml parses successfully as a list/string/number instead of a mapping, later call sites do manifest.get(...) and crash.

This is not just malformed YAML; it is valid YAML with the wrong top-level type, so the current exception handling does not protect the install flow.

Minimal reproduction

source venv/bin/activate
python - <<'PY'
from pathlib import Path
from unittest.mock import patch, MagicMock
import tempfile
import hermes_cli.plugins_cmd as pc

with tempfile.TemporaryDirectory() as tmp:
    plugins_dir = Path(tmp) / 'plugins'
    plugins_dir.mkdir()
    with patch('hermes_cli.plugins_cmd._plugins_dir', return_value=plugins_dir), \
         patch('hermes_cli.plugins_cmd.subprocess.run', return_value=MagicMock(returncode=0, stdout='', stderr='')), \
         patch('hermes_cli.plugins_cmd._read_manifest', return_value=['not', 'a', 'dict']), \
         patch('hermes_cli.plugins_cmd._display_after_install'):
        pc.cmd_install('owner/repo')
PY

Actual result:

AttributeError: 'list' object has no attribute 'get'

Expected behavior:

  • treat non-dict manifests as invalid and surface a friendly plugin manifest error, or
  • coerce them to {} and continue with repo-name fallback.

Additional evidence

Current tests only cover invalid YAML syntax / empty files:

  • tests/hermes_cli/test_plugins_cmd.py:199-209

There is no coverage for valid-but-non-mapping YAML, which is why this crash path slipped through.

Suggested investigation

Harden _read_manifest() (and the direct yaml.safe_load(...).get(...) call sites in cmd_list / composite plugin UI / PluginRegistry._scan_directory) so they verify isinstance(manifest, dict) before accessing .get().

extent analysis

TL;DR

The most likely fix is to modify the _read_manifest() function to verify that the loaded YAML is a dictionary before attempting to access its elements.

Guidance

  • Modify the _read_manifest() function to check if the loaded YAML is a dictionary using isinstance(manifest, dict) before returning it.
  • Update the cmd_list and PluginRegistry._scan_directory functions to perform the same check before accessing the .get() method.
  • Consider adding test cases to cover valid-but-non-mapping YAML scenarios to prevent similar issues in the future.
  • Review the plugin.yaml parsing logic to ensure it correctly handles different YAML types and provides informative error messages for invalid manifests.

Example

def _read_manifest(plugin_dir):
    manifest_path = plugin_dir / 'plugin.yaml'
    with open(manifest_path, 'r') as f:
        manifest = yaml.safe_load(f)
    if not isinstance(manifest, dict):
        raise ValueError("Invalid plugin manifest: expected a dictionary")
    return manifest

Notes

The current implementation assumes that the plugin.yaml file always contains a dictionary, which is not always the case. By adding a simple type check, we can prevent the AttributeError and provide a more informative error message to the user.

Recommendation

Apply the workaround by modifying the _read_manifest() function and related call sites to verify the type of the loaded YAML, as this will provide a more robust and informative error handling mechanism.

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