NousResearch/hermes-agent

Kanban dispatcher spawn path fix

Cluster 23184. Data as of

Nothing open on GitHub. Fix merged in #23222 on 10 May 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #23222. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this unresolved. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and PRs address a bug where the Kanban dispatcher fails to spawn correctly due to PATH or executable resolution problems. The fixes involve changing how the dispatcher process is launched, targeting compatibility with environments like NixOS.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #23222 fix(kanban): PATH-independent dispatcher spawn (salvage #23198)

    merged to main

    files: hermes_cli/kanban_db.py, tests/hermes_cli/test_kanban_db.py

    Show the diff
    • hermes_cli/kanban_db.py
      @@ -3749,6 +3749,35 @@ def _rotate_worker_log(log_path: Path, max_bytes: int) -> None:
               pass
       
       
      +def _resolve_hermes_argv() -> list[str]:
      +    """Resolve the ``hermes`` invocation as argv parts for ``Popen``.
      +
      +    Tries in order:
      +
      +    1. ``shutil.which("hermes")`` — the console-script shim, the same form
      +       that shows up in ``ps`` output and existing logs. Preferred so live
      +       systems' diagnostics stay familiar.
      +    2. ``sys.executable -m hermes_cli.main`` — fallback for setups where
      +       Hermes is launched from a venv and the ``hermes`` shim is not on
      +       the dispatcher's ``$PATH`` (cron, systemd ``User=`` services,
      +       launchd jobs, detached processes, etc.). Goes through the running
      +       interpreter so the result is independent of ``$PATH``.
      +
      +    Mirrors ``gateway.run._resolve_hermes_bin`` for the same reason. Kept
      +    local (not imported from gateway) because ``hermes_cli`` sits below
      +    ``gateway`` in the dependency order.
      +    """
      +    import shutil
      +
      +    hermes_bin = shutil.which("hermes")
      +    if hermes_bin:
      +        return [hermes_bin]
      +    # Fallback to the module form. ``hermes_cli.main`` is the actual
      +    # console-script target declared in pyproject.toml, NOT a top-level
      +    # ``hermes`` package — there is no ``hermes`` package to import.
      +    return [sys.executable, "-m", "hermes_cli.main"]
      +
      +
       def _default_spawn(
           task: Task,
           workspace: str,
      @@ -3805,7 +3834,7 @@ def _default_spawn(
           env["HERMES_PROFILE"] = profile_arg
       
           cmd = [
      -        "hermes",
      +        *_resolve_hermes_argv(),
               "-p", profile_arg,
               # Auto-load the kanban-worker skill so every dispatched worker
               # has the pattern library (good summary/metadata shapes, retry
    • tests/hermes_cli/test_kanban_db.py
      @@ -1199,3 +1199,68 @@ def test_migrate_add_optional_columns_tolerates_concurrent_migration(kanban_home
           # Running migration on an already-migrated schema must not raise.
           kb._migrate_add_optional_columns(conn)
           conn.close()
      +
      +
      +# ---------------------------------------------------------------------------
      +# Dispatcher spawn invocation — _resolve_hermes_argv()
      +#
      +# Workers spawned by the dispatcher must use a `hermes` invocation that does
      +# not depend on PATH being set up correctly. cron jobs, systemd User= services,
      +# launchd jobs, and other detached processes routinely run with a stripped
      +# $PATH that doesn't include the venv's bin/, so a bare `["hermes", ...]`
      +# spawn fails with FileNotFoundError and the task gets stuck. The resolver
      +# prefers the PATH shim (familiar `ps` output) but falls back to the module
      +# form so the spawn keeps working when PATH is missing the shim.
      +# ---------------------------------------------------------------------------
      +
      +
      +def test_resolve_hermes_argv_prefers_path_shim(monkeypatch):
      +    """When `hermes` is on PATH, use the shim — preserves familiar ps output."""
      +    import shutil
      +    import hermes_cli.kanban_db as kb
      +
      +    monkeypatch.setattr(shutil, "which", lambda name: "/usr/local/bin/hermes")
      +    argv = kb._resolve_hermes_argv()
      +    assert argv == ["/usr/local/bin/hermes"]
      +
      +
      +def test_resolve_hermes_argv_falls_back_to_module_form_when_no_path_shim(monkeypatch):
      +    """When the shim is not on PATH, fall back to `python -m hermes_cli.main`.
      +
      +    Pins the correct module name (NOT `hermes` — there is no top-level
      +    `hermes` package). Regression for #23198: the original PR shipped
      +    `python -m hermes` which fails with `No module named hermes` on every
      +    invocation.
      +    """
      +    import shutil
      +    import sys
      +    import hermes_cli.kanban_db as kb
      +
      +    monkeypatch.setattr(shutil, "which", lambda name: None)
      +    argv = kb._resolve_hermes_argv()
      +    assert argv == [sys.executable, "-m", "hermes_cli.main"]
      +
      +
      +def test_resolve_hermes_argv_module_actually_runs():
      +    """The fallback module name must be importable + runnable.
      +
      +    A unit test that pins the literal string is necessary but not
      +    sufficient — if `hermes_cli.main` ever loses `if __name__ == "__main__"`
      +    handling or its argparse setup, `python -m hermes_cli.main --version`
      +    would fail and so would every dispatcher spawn that hits the fallback.
      +    Run it as a real subprocess to catch that regression.
      +    """
      +    import subprocess
      +    import sys
      +    import hermes_cli.kanban_db as kb
      +    import shutil
      +    import unittest.mock as mock
      +
      +    with mock.patch.object(shutil, "which", return_value=None):
      +        argv = kb._resolve_hermes_argv()
      +    r = subprocess.run(argv + ["--version"], capture_output=True, text=True, timeout=30)
      +    assert r.returncode == 0, (
      +        f"`{' '.join(argv)} --version` failed (rc={r.returncode}); "
      +        f"stderr={r.stderr[:200]!r}"
      +    )
      +    assert "Hermes Agent" in r.stdout, f"unexpected output: {r.stdout[:200]!r}"

1 PR closed without merging

From the close comments: merged via another change 1

  • #23198 fix(kanban): use sys.executable -m hermes for dispatcher spawn · merged via another change → #23222, #1049
Closed issues · 1
  • Closed#23184[Bug]: Unable to use Kanban with nixos module
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#23222
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
10 May 2026

For your agent

The same facts, typed.