{"repo":"NousResearch/hermes-agent","public_id":"33712","display_label":"Missing hermes_cli subpackage in wheel/packaging","summary":"Multiple bug reports and fix attempts center on hermes_cli subpackages (particularly dashboard_auth) being absent or misplaced in distributed wheels, causing ModuleNotFoundError when running the dashboard. Proposed fixes include packaging corrections, lazy imports, and pre-publish wheel verification.","subsystem":"dashboard","counts":{"members":12,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":7,"merged_prs":1,"attempt_prs":4},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":34811},"resolution":{"state":"partially_resolved","by":"pr-34811"},"freshness":{"state":"verified","last_verified_at":"2026-05-29T19:36:10+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.15757575757575756},"fix_edges":[{"issue_number":34701,"pr_number":34811,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":33712,"title":"[Bug]: Hermes dashboard not working on NixOS (container)","url":null,"state":"closed"},{"kind":"issue","number":34346,"title":"v0.15.1 wheel installs hermes_cli/dashboard_auth/ to wrong path → dashboard ModuleNotFoundError","url":null,"state":"closed"},{"kind":"issue","number":34701,"title":"[Bug]: Dashboard fails after updating to 0.15.2: missing hermes_cli.dashboard_auth module","url":null,"state":"closed"},{"kind":"issue","number":34857,"title":"[Bug]: Nix package missing hermes_cli.dashboard_auth, causing `hermes dashboard` to fail","url":null,"state":"closed"},{"kind":"issue","number":36743,"title":"[Bug]: v0.15.2 wheel missing hermes_cli/dashboard_auth — dashboard crashes on startup","url":null,"state":"closed"},{"kind":"issue","number":39631,"title":"Dashboard fails to start on v0.15.2: ModuleNotFoundError: No module named 'hermes_cli.dashboard_auth'","url":null,"state":"closed"},{"kind":"issue","number":39674,"title":"bug: ModuleNotFoundError 'hermes_cli.dashboard_auth' when running  (PyPI install)","url":null,"state":"closed"},{"kind":"pr","number":34505,"title":"fix(wheel): include hermes_cli subpackages in wheel build","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#27728","number":27728}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":34717,"title":"fix(packaging): include hermes_cli subpackages in wheel build","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#27728","number":27728}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":34811,"title":"fix(packaging): ship hermes_cli subpackages in wheel","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["pyproject.toml","tests/test_packaging_metadata.py"],"fix_hunks":[{"file":"pyproject.toml","hunks":["@@ -237,7 +237,7 @@ plugins = [\n ]\n \n [tool.setuptools.packages.find]\n-include = [\"agent\", \"agent.*\", \"tools\", \"tools.*\", \"hermes_cli\", \"gateway\", \"gateway.*\", \"tui_gateway\", \"tui_gateway.*\", \"cron\", \"acp_adapter\", \"plugins\", \"plugins.*\", \"providers\", \"providers.*\"]\n+include = [\"agent\", \"agent.*\", \"tools\", \"tools.*\", \"hermes_cli\", \"hermes_cli.*\", \"gateway\", \"gateway.*\", \"tui_gateway\", \"tui_gateway.*\", \"cron\", \"acp_adapter\", \"plugins\", \"plugins.*\", \"providers\", \"providers.*\"]\n \n [tool.pytest.ini_options]\n testpaths = [\"tests\"]"]},{"file":"tests/test_packaging_metadata.py","hunks":["@@ -1,10 +1,59 @@\n from pathlib import Path\n import tomllib\n \n+from setuptools import find_packages\n+\n \n REPO_ROOT = Path(__file__).resolve().parents[1]\n \n \n+def _packages_find_include():\n+    data = tomllib.loads((REPO_ROOT / \"pyproject.toml\").read_text(encoding=\"utf-8\"))\n+    return data[\"tool\"][\"setuptools\"][\"packages\"][\"find\"][\"include\"]\n+\n+\n+def test_every_on_disk_subpackage_is_covered_by_packages_find():\n+    \"\"\"Regression test for #34701 (and the bug class behind #34034 / #28149).\n+\n+    ``[tool.setuptools.packages.find]`` ``include`` is hand-maintained. Every\n+    top-level package is listed twice — bare (``hermes_cli``) for the package\n+    itself and ``hermes_cli.*`` for its subpackages — EXCEPT when someone\n+    forgets the wildcard. v0.15.x listed ``hermes_cli`` without ``hermes_cli.*``,\n+    so the wheel shipped ``hermes_cli/*.py`` but dropped the ``dashboard_auth``\n+    and ``proxy`` subpackages. The dashboard then died on every install with\n+    ``ModuleNotFoundError: No module named 'hermes_cli.dashboard_auth'``.\n+\n+    This drives setuptools' own discovery against the live tree: every package\n+    that exists on disk and would be found by a permissive ``<name>.*`` scan\n+    must also be found by the actual ``include`` list. A subpackage added under\n+    any listed package without the matching wildcard fails here instead of in a\n+    user's container.\n+    \"\"\"\n+    include = _packages_find_include()\n+\n+    # What the real include list actually selects.\n+    selected = set(find_packages(where=str(REPO_ROOT), include=include))\n+\n+    # Top-level packages we ship (bare names in the include list, no wildcard).\n+    top_level = sorted({name for name in include if \".\" not in name})\n+\n+    # For each shipped top-level package, every on-disk subpackage must be\n+    # covered by the include list.\n+    expected = set(\n+        find_packages(\n+            where=str(REPO_ROOT),\n+            include=[pattern for name in top_level for pattern in (name, f\"{name}.*\")],\n+        )\n+    )\n+\n+    missing = sorted(expected - selected)\n+    assert not missing, (\n+        \"These packages exist on disk but are dropped from the wheel because \"\n+        \"[tool.setuptools.packages.find] include is missing a wildcard. Add the \"\n+        f\"matching '<name>.*' entry in pyproject.toml: {missing}\"\n+    )\n+\n+\n def test_faster_whisper_is_not_a_base_dependency():\n     data = tomllib.loads((REPO_ROOT / \"pyproject.toml\").read_text(encoding=\"utf-8\"))\n     deps = data[\"project\"][\"dependencies\"]\n"]}]},{"kind":"pr","number":37334,"title":"fix(packaging): verify wheel contents before PyPI publish to catch missing subpackages","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#36743","number":36743}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":39873,"title":"fix(dashboard): lazy-import dashboard_auth so PyPI install doesn't crash","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}