Kanban column label undefined aria-label bug fixes

NousResearch/hermes-agent · cluster 23620 · hermes cli

Data as of

Nothing open on GitHub. Fix merged in #23791. Check it covers your case.

The short answer

0open issues
0open PRs
6items grouped
Known?
Yes. 6 related items grouped here, 2 issues already closed.
Fix event
A fix was merged to main in PR #23791. 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.

Multiple reports and fix attempts address an undefined COLUMN_LABEL variable causing rendering errors and broken aria-labels on kanban column select-all checkboxes. One unrelated member covers SPA auto-reload on stale-token 401 responses.

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.

  • #23791 fix(kanban): batch-salvage 7 community kanban bug fixes

    merged to main

    files: gateway/run.py, hermes_cli/kanban.py, hermes_cli/kanban_db.py, plugins/kanban/dashboard/dist/index.js, run_agent.py, scripts/release.py, tests/hermes_cli/test_kanban_cli.py, tests/hermes_cli/test_kanban_notify.py, tests/run_agent/test_run_agent.py, tests/tools/test_send_message_tool.py, tools/send_message_tool.py

    Show the diff
    • gateway/run.py
      @@ -8329,6 +8329,7 @@ async def _handle_kanban_command(self, event: MessageEvent) -> str:
               """
               import asyncio
               import re
      +        import shlex
               from hermes_cli.kanban import run_slash
       
               text = (event.text or "").strip()
      @@ -8338,7 +8339,26 @@ async def _handle_kanban_command(self, event: MessageEvent) -> str:
               if text.startswith("kanban"):
                   text = text[len("kanban"):].lstrip()
       
      -        is_create = text.split(None, 1)[:1] == ["create"]
      +        tokens = [redacted:secret-assignment] if text else []
      +        requested_board = None
      +        action = None
      +        i = 0
      +        while i < len(tokens):
      +            tok = tokens[i]
      +            if tok == "--board":
      +                if i + 1 >= len(tokens):
      +                    break
      +                requested_board = tokens[i + 1]
      +                i += 2
      +                continue
      +            if tok.startswith("--board="):
      +                requested_board = tok.split("=", 1)[1]
      +                i += 1
      +                continue
      +            action = tok
      +            break
      +
      +        is_create = action == "create"
       
               try:
                   output = await asyncio.to_thread(run_slash, text)
      @@ -8365,7 +8385,7 @@ async def _handle_kanban_command(self, event: MessageEvent) -> str:
                           if platform_str and chat_id:
                               def _sub():
                                   from hermes_cli import kanban_db as _kb
      -                            conn = _kb.connect()
      +                            conn = _kb.connect(board=requested_board)
                                   try:
                                       _kb.add_notify_sub(
                                           conn, task_id=task_id,
    • hermes_cli/kanban.py
      @@ -652,6 +652,16 @@ def kanban_command(args: argparse.Namespace) -> int:
           # keeps the patch small and inherits the exact same resolution the
           # dispatcher uses for workers — consistency is a feature here.
           board_override = getattr(args, "board", None)
      +    prev_board_env = os.environ.get("HERMES_KANBAN_BOARD")
      +    restore_board_env = False
      +
      +    def _restore_board_env() -> None:
      +        if not restore_board_env:
      +            return
      +        if prev_board_env is None:
      +            os.environ.pop("HERMES_KANBAN_BOARD", None)
      +        else:
      +            os.environ["HERMES_KANBAN_BOARD"] = prev_board_env
           if board_override:
               try:
                   normed = kb._normalize_board_slug(board_override)
      @@ -671,12 +681,16 @@ def kanban_command(args: argparse.Namespace) -> int:
                   )
                   return 1
               os.environ["HERMES_KANBAN_BOARD"] = normed
      +        restore_board_env = True
       
           # Boards management doesn't touch the DB at all — dispatch early so
           # fresh installs that haven't initialized any DB can still use
           # `hermes kanban boards create …`.
           if action == "boards":
      -        return _dispatch_boards(args)
      +        try:
      +            return _dispatch_boards(args)
      +        finally:
      +            _restore_board_env()
       
           # Auto-initialize the DB before dispatching any subcommand. init_db
           # is idempotent, so running it every invocation is cheap (one
      @@ -689,6 +703,7 @@ def kanban_command(args: argparse.Namespace) -> int:
               kb.init_db()
           except Exception as exc:
               print(f"kanban: could not initialize database: {exc}", file=sys.stderr)
      +        _restore_board_env()
               return 1
       
           handlers = {
      @@ -730,12 +745,16 @@ def kanban_command(args: argparse.Namespace) -> int:
           handler = handlers.get(action)
           if not handler:
               print(f"kanban: unknown action {action!r}", file=sys.stderr)
      +        _restore_board_env()
               return 2
           try:
               return int(handler(args) or 0)
           except (ValueError, RuntimeError) as exc:
               print(f"kanban: {exc}", file=sys.stderr)
      +        _restore_board_env()
               return 1
      +    finally:
      +        _restore_board_env()
       
       
       # ---------------------------------------------------------------------------
    • hermes_cli/kanban_db.py
      @@ -1826,7 +1826,7 @@ def _synthesize_ended_run(
       # ---------------------------------------------------------------------------
       
       def recompute_ready(conn: sqlite3.Connection) -> int:
      -    """Promote ``todo`` tasks to ``ready`` when all parents are ``done``.
      +    """Promote ``todo`` tasks to ``ready`` when all parents are ``done`` or ``archived``.
       
           Returns the number of tasks promoted.  Safe to call inside or outside
           an existing transaction; it opens its own IMMEDIATE txn.
      @@ -1844,7 +1844,7 @@ def recompute_ready(conn: sqlite3.Connection) -> int:
                       "WHERE l.child_id = ?",
                       (task_id,),
                   ).fetchall()
      -            if all(p["status"] == "done" for p in parents):
      +            if all(p["status"] in ("done", "archived") for p in parents):
                       conn.execute(
                           "UPDATE tasks SET status = 'ready' WHERE id = ? AND status = 'todo'",
                           (task_id,),
      @@ -1885,7 +1885,7 @@ def claim_task(
               undone = conn.execute(
                   "SELECT 1 FROM task_links l "
                   "JOIN tasks p ON p.id = l.parent_id "
      -            "WHERE l.child_id = ? AND p.status != 'done' LIMIT 1",
      +            "WHERE l.child_id = ? AND p.status NOT IN ('done', 'archived') LIMIT 1",
                   (task_id,),
               ).fetchone()
               if undone:
      @@ -3930,6 +3930,25 @@ def _default_spawn(
       
           prompt = f"work kanban task {task.id}"
           env = dict(os.environ)
      +
      +    # Inject HERMES_HOME so the worker reads the profile-scoped config.yaml
      +    # (fallback_providers, toolsets, agent settings, etc.) instead of the root
      +    # config.  Without this, `env = dict(os.environ)` copies only the parent's
      +    # env, and when the child process starts `hermes -p <name>` the
      +    # _apply_profile_override() runs *before* hermes_constants is imported.
      +    # If HERMES_HOME is absent from the child's env, get_hermes_home() falls
      +    # back to Path.home() / ".hermes" (the DEFAULT profile root), ignoring the
      +    # profile-specific config entirely.  Fixes profile-scoped fallback_providers
      +    # being invisible to kanban workers.
      +    from hermes_cli.profiles import resolve_profile_env
      +    try:
      +        env["HERMES_HOME"] = resolve_profile_env(profile_arg)
      +    except FileNotFoundError:
      +        # Profile dir doesn't exist — defer resolution to the CLI's
      +        # _apply_profile_override() via HERMES_PROFILE (set below).
      +        # This only happens in test fixtures where the isolated
      +        # HERMES_HOME never had profiles created.
      +        pass
           if task.tenant:
               env["HERMES_TENANT"] = task.tenant
           env["HERMES_KANBAN_TASK"] = task.id
    • plugins/kanban/dashboard/dist/index.js
      @@ -1898,7 +1898,7 @@
                 type: "checkbox",
                 className: "hermes-kanban-col-check",
                 title: "Select all tasks in this column",
      -          "aria-label": `Select all tasks in ${COLUMN_LABEL[props.column.name] || props.column.name}`,
      +          "aria-label": `Select all tasks in ${colLabel || props.column.name}`,
                 checked: props.column.tasks.length > 0 && props.column.tasks.every(function (t) { return props.selectedIds.has(t.id); }),
                 onChange: function (e) {
                   e.stopPropagation();
    • run_agent.py
      @@ -14987,7 +14987,41 @@ def _stop_spinner():
                           "— requesting summary..."
                       )
                   final_response = self._handle_max_iterations(messages, api_call_count)
      -        
      +
      +            # If running as a kanban worker, block the task so the dispatcher
      +            # knows the worker could not complete (rather than treating it as a
      +            # protocol violation).  The agent loop strips tools before calling
      +            # _handle_max_iterations, so the model cannot call kanban_block
      +            # itself — we must do it on its behalf.
      +            _kanban_task = os.environ.get("HERMES_KANBAN_TASK")
      +            if _kanban_task:
      +                try:
      +                    handle_function_call(
      +                        "kanban_block",
      +                        {
      +                            "task_id": _kanban_task,
      +                            "reason": (
      +                                f"Iteration budget exhausted "
      +                                f"({api_call_count}/{self.max_iterations}) — "
      +                                "task could not complete within the allowed "
      +                                "iterations"
      +                            ),
      +                        },
      +                        task_id=effective_task_id,
      +                    )
      +                    logger.info(
      +                        "kanban_block called for task %s after iteration "
      +                        "exhaustion (%d/%d)",
      +                        _kanban_task, api_call_count, self.max_iterations,
      +                    )
      +                except Exception:
      +                    logger.warning(
      +                        "Failed to call kanban_block after iteration "
      +                        "exhaustion for task %s",
      +                        _kanban_task,
      +                        exc_info=True,
      +                    )
      +
               # Determine if conversation completed successfully
               completed = final_response is not None and api_call_count < self.max_iterations
       
    • scripts/release.py
      @@ -971,6 +971,12 @@
           "[redacted:email]": "leehack",  # PR #22053 salvage (telegram DM topic reply fallback)
           # pander: empty email, salvaged via PR #19665 from #16126 by @ms-alan
           "[redacted:email]": "A-kamal",  # PR #18678 (xAI image resolution fix)
      +    # Kanban bug-fix batch salvage (May 2026)
      +    "[redacted:email]": "Frowtek",  # salvage of #23206 (gateway --board auto-subscribe)
      +    "[redacted:email]": "Sylw3ster",  # salvage of #23252 (HERMES_KANBAN_BOARD restore)
      +    "[redacted:email]": "dmnkhorvath",  # salvage of #23358 (kanban worker send_message)
      +    "[redacted:email]": "smwbev",  # salvage of #23659 (aria-label colLabel)
      +    "[redacted:email]": "TurgutKural",  # salvage of #23356 (HERMES_HOME inject)
       }
       
       
    • tests/hermes_cli/test_kanban_cli.py
      @@ -392,3 +392,13 @@ def test_run_slash_missing_required_arg_friendly_error(kanban_home):
           out = kc.run_slash("show")
           assert "/kanban show" in out
           assert "task_id" in out
      +
      +
      +def test_run_slash_board_override_restores_prior_env(kanban_home, monkeypatch):
      +    kb.create_board("alpha")
      +    kb.create_board("beta")
      +    monkeypatch.setenv("HERMES_KANBAN_BOARD", "beta")
      +
      +    kc.run_slash("--board alpha list")
      +
      +    assert os.environ.get("HERMES_KANBAN_BOARD") == "beta"
    • tests/hermes_cli/test_kanban_notify.py
      @@ -2,6 +2,7 @@
       import pytest
       
       from pathlib import Path
      +from types import SimpleNamespace
       from hermes_cli import kanban_db as kb
       from unittest.mock import AsyncMock, MagicMock, patch
       
      @@ -429,3 +430,52 @@ async def _fast_sleep(_):
           finally:
               conn.close()
           assert subs == []
      +
      +
      [redacted:email]
      +async def test_gateway_create_autosubscribes_on_explicit_board(kanban_home):
      +    """`/kanban --board <slug> create ...` must subscribe on that board.
      +
      +    The gateway handler currently auto-subscribes after `/kanban create`,
      +    but the create detection must still work when the shared `--board`
      +    flag appears before the subcommand, and the subscription must land in
      +    that board's DB rather than the ambient/default board.
      +    """
      +    from gateway.run import GatewayRunner
      +    from gateway.config import Platform
      +
      +    kb.create_board("projx")
      +
      +    runner = object.__new__(GatewayRunner)
      +    source = SimpleNamespace(
      +        platform=Platform.TELEGRAM,
      +        chat_id="chat1",
      +        thread_id="th1",
      +        user_id="u1",
      +    )
      +    event = SimpleNamespace(
      +        text='/kanban --board projx create "hello" --assignee alice',
      +        source=source,
      +    )
      +
      +    out = await GatewayRunner._handle_kanban_command(runner, event)
      +
      +    assert "subscribed" in out.lower()
      +
      +    conn = kb.connect(board="projx")
      +    try:
      +        subs = kb.list_notify_subs(conn)
      +        tasks = kb.list_tasks(conn)
      +    finally:
      +        conn.close()
      +
      +    assert [t.title for t in tasks] == ["hello"]
      +    assert len(subs) == 1
      +    assert subs[0]["chat_id"] == "chat1"
      +    assert subs[0]["thread_id"] == "th1"
      +
      +    conn = kb.connect(board="default")
      +    try:
      +        assert kb.list_notify_subs(conn) == []
      +    finally:
      +        conn.close()
    • tests/run_agent/test_run_agent.py
      @@ -3344,6 +3344,88 @@ def test_truncated_tool_args_detected_when_finish_reason_not_length(self, agent)
               assert "truncated due to output length limit" in result["error"]
               mock_handle_function_call.assert_not_called()
       
      +    def test_kanban_block_called_on_iteration_exhaustion(self, agent, monkeypatch):
      +        """Regression: kanban worker must call kanban_block when iteration
      +        budget is exhausted, otherwise the dispatcher sees a protocol
      +        violation and gives up after 1 failure (issue #23216)."""
      +        self._setup_agent(agent)
      +        agent.max_iterations = 2
      +
      +        monkeypatch.setenv("HERMES_KANBAN_TASK", "t_test_task_123")
      +
      +        # Return a tool call for every iteration to exhaust the budget.
      +        tc = _mock_tool_call(name="web_search", arguments="{}", call_id="c1")
      +        tool_resp = _mock_response(
      +            content="", finish_reason="tool_calls", tool_calls=[tc],
      +        )
      +        # Final summary response from _handle_max_iterations.
      +        summary_resp = _mock_response(
      +            content="Could not finish — budget exhausted.", finish_reason="stop",
      +        )
      +        agent.client.chat.completions.create.side_effect = [
      +            tool_resp, tool_resp, summary_resp,
      +        ]
      +
      +        with (
      +            patch("run_agent.handle_function_call", return_value="ok") as mock_hfc,
      +            patch.object(agent, "_persist_session"),
      +            patch.object(agent, "_save_trajectory"),
      +            patch.object(agent, "_cleanup_task_resources"),
      +        ):
      +            result = agent.run_conversation("do the kanban work")
      +
      +        # The agent should have reported the task as not completed.
      +        assert result["completed"] is False
      +
      +        # Among all handle_function_call invocations, one must be
      +        # kanban_block with the correct task_id and a reason mentioning
      +        # iteration exhaustion.
      +        kanban_block_calls = [
      +            c for c in mock_hfc.call_args_list
      +            if c[0][0] == "kanban_block"
      +        ]
      +        assert len(kanban_block_calls) == 1, (
      +            f"Expected exactly 1 kanban_block call, got {len(kanban_block_calls)}. "
      +            f"All calls: {mock_hfc.call_args_list}"
      +        )
      +        call = kanban_block_calls[0]
      +        assert call[0][1]["task_id"] == "t_test_task_123"
      +        assert "Iteration budget exhausted" in call[0][1]["reason"]
      +
      +    def test_no_kanban_block_when_not_in_kanban_mode(self, agent, monkeypatch):
      +        """kanban_block must NOT be called when HERMES_KANBAN_TASK is unset."""
      +        self._setup_agent(agent)
      +        agent.max_iterations = 2
      +
      +        monkeypatch.delenv("HERMES_KANBAN_TASK", raising=False)
      +
      +        tc = _mock_tool_call(name="web_search", arguments="{}", call_id="c1")
      +        tool_resp = _mock_response(
      +            content="", finish_reason="tool_calls", tool_calls=[tc],
      +        )
      +        summary_resp = _mock_response(
      +            content="Summary.", finish_reason="stop",
      +        )
      +        agent.client.chat.completions.create.side_effect = [
      +            tool_resp, tool_resp, summary_resp,
      +        ]
      +
      +        with (
      +            patch("run_agent.handle_function_call", return_value="ok") as mock_hfc,
      +            patch.object(agent, "_persist_session"),
      +            patch.object(agent, "_save_trajectory"),
      +            patch.object(agent, "_cleanup_task_resources"),
      +        ):
      +            agent.run_conversation("do stuff")
      +
      +        kanban_block_calls = [
      +            c for c in mock_hfc.call_args_list
      +            if c[0][0] == "kanban_block"
      +        ]
      +        assert len(kanban_block_calls) == 0, (
      +            "kanban_block should not be called outside kanban mode"
      +        )
      +
       
       class TestRetryExhaustion:
           """Regression: retry_count > max_retries was dead code (off-by-one).
    • tests/tools/test_send_message_tool.py
      @@ -2229,3 +2229,106 @@ async def fake_send(pconfig, chat_id, message, **kwargs):
               assert result["success"] is True
               assert result["message_id"] == "abc-123"
               assert result["extra_field"] == "preserved"
      +
      +
      +# ---------------------------------------------------------------------------
      +# _check_send_message — availability gating
      +# ---------------------------------------------------------------------------
      +
      +class TestCheckSendMessage:
      +    """The tool's check_fn governs whether the model sees ``send_message`` as
      +    callable for a given session. The four passing conditions are:
      +
      +    1. ``HERMES_KANBAN_TASK`` is set (worker spawned by the kanban dispatcher
      +       — parent gateway is by definition running, but the worker's
      +       ``HERMES_HOME`` may be a profile dir without a ``gateway.pid``).
      +    2. ``HERMES_SESSION_PLATFORM`` resolves to a non-empty, non-``local`` value
      +       (the session is wired to a messaging platform like Telegram).
      +    3. ``is_gateway_running()`` returns True (CLI / orchestrator profile with
      +       a live gateway colocated under the same ``HERMES_HOME``).
      +    4. None of the above → False, tool is hidden.
      +    """
      +
      +    def test_kanban_task_env_grants_access(self, monkeypatch):
      +        """Workers spawned by the dispatcher (HERMES_KANBAN_TASK set) must be
      +        allowed regardless of session_platform / gateway-pid state."""
      +        from tools.send_message_tool import _check_send_message
      +
      +        monkeypatch.setenv("HERMES_KANBAN_TASK", "t_abc12345")
      +        monkeypatch.delenv("HERMES_SESSION_PLATFORM", raising=False)
      +
      +        with patch("gateway.session_context.get_session_env", return_value=""), \
      +             patch("gateway.status.is_gateway_running", return_value=False):
      +            assert _check_send_message() is True
      +
      +    def test_kanban_task_env_short_circuits_before_gateway_check(self, monkeypatch):
      +        """Honoring HERMES_KANBAN_TASK must not depend on importing or calling
      +        gateway.status — the worker may run with a HERMES_HOME that has no
      +        gateway.pid, and we don't want that import path to be load-bearing."""
      +        from tools.send_message_tool import _check_send_message
      +
      +        monkeypatch.setenv("HERMES_KANBAN_TASK", "t_abc12345")
      +
      +        with patch("gateway.session_context.get_session_env",
      +                   side_effect=AssertionError("session_context not consulted "
      +                                              "when HERMES_KANBAN_TASK is set")), \
      +             patch("gateway.status.is_gateway_running",
      +                   side_effect=AssertionError("gateway.status not consulted "
      +                                              "when HERMES_KANBAN_TASK is set")):
      +            assert _check_send_message() is True
      +
      +    def test_messaging_platform_session_grants_access(self, monkeypatch):
      +        """Telegram/Discord/etc. sessions pass via the platform branch even
      +        without HERMES_KANBAN_TASK."""
      +        from tools.send_message_tool import _check_send_message
      +
      +        monkeypatch.delenv("HERMES_KANBAN_TASK", raising=False)
      +
      +        with patch("gateway.session_context.get_session_env", return_value="telegram"), \
      +             patch("gateway.status.is_gateway_running", return_value=False):
      +            assert _check_send_message() is True
      +
      +    def test_local_platform_falls_through_to_gateway_check(self, monkeypatch):
      +        """``HERMES_SESSION_PLATFORM=local`` means CLI-style — must defer to
      +        is_gateway_running() rather than auto-grant."""
      +        from tools.send_message_tool import _check_send_message
      +
      +        monkeypatch.delenv("HERMES_KANBAN_TASK", raising=False)
      +
      +        with patch("gateway.session_context.get_session_env", return_value="local"), \
      +             patch("gateway.status.is_gateway_running", return_value=True) as gw_mock:
      +            assert _check_send_message() is True
      +            gw_mock.assert_called_once()
      +
      +    def test_running_gateway_grants_access(self, monkeypatch):
      +        """Plain CLI session (no kanban task, empty platform) with a live
      +        gateway: tool is callable."""
      +        from tools.send_message_tool import _check_send_message
      +
      +        monkeypatch.delenv("HERMES_KANBAN_TASK", raising=False)
      +
      +        with patch("gateway.session_context.get_session_env", return_value=""), \
      +             patch("gateway.status.is_gateway_running", return_value=True):
      +            assert _check_send_message() is True
      +
      +    def test_no_signals_means_unavailable(self, monkeypatch):
      +        """No kanban task, no platform, no gateway: tool is hidden."""
      +        from tools.send_message_tool import _check_send_message
      +
      +        monkeypatch.delenv("HERMES_KANBAN_TASK", raising=False)
      +
      +        with patch("gateway.session_context.get_session_env", return_value=""), \
      +             patch("gateway.status.is_gateway_running", return_value=False):
      +            assert _check_send_message() is False
      +
      +    def test_gateway_status_import_error_is_swallowed(self, monkeypatch):
      +        """If gateway.status can't be imported (unusual deployment / partial
      +        install), the check returns False rather than raising."""
      +        from tools.send_message_tool import _check_send_message
      +
      +        monkeypatch.delenv("HERMES_KANBAN_TASK", raising=False)
      +
      +        with patch("gateway.session_context.get_session_env", return_value=""), \
      +             patch("gateway.status.is_gateway_running",
      +                   side_effect=ImportError("simulated")):
      +            assert _check_send_message() is False
    • tools/send_message_tool.py
      @@ -1757,7 +1757,20 @@ async def _send_feishu(pconfig, chat_id, message, media_files=None, thread_id=No
       
       
       def _check_send_message():
      -    """Gate send_message on gateway running (always available on messaging platforms)."""
      +    """Gate send_message on gateway running (always available on messaging platforms).
      +
      +    Also passes for kanban workers — the dispatcher sets ``HERMES_KANBAN_TASK``
      +    on every spawned worker, but those workers run with the assignee profile's
      +    ``HERMES_HOME`` which has no ``gateway.pid``, so the gateway-running check
      +    would fail even though the parent gateway is alive. Honoring the env var
      +    lets workers call ``send_message`` to deliver rich content directly to the
      +    originating chat (paired with ``kanban_complete`` for the short notifier
      +    summary), which is the canonical pattern for any worker that needs to
      +    reply with more than the ~200-char first-line truncation the kanban
      +    notifier applies.
      +    """
      +    if os.environ.get("HERMES_KANBAN_TASK"):
      +        return True
           from gateway.session_context import get_session_env
           platform = get_session_env("HERMES_SESSION_PLATFORM", "")
           if platform and platform != "local":
  • #33861 fix(dashboard): auto-reload SPA on stale-token 401 in loopback mode

    merged to mainSuggested link · files and diff not shown

2 PRs closed without merging

From the close comments: merged via another change 2

  • #23637 fix(kanban): use FALLBACK_COLUMN_LABEL in column select-all aria-label · merged via another change → #23791, #23659
  • #23659 fix(kanban): use localized column label in select-all aria label · merged via another change → #23791
Closed issues · 2
  • Closed#23620[Bug]: Kanban dashboard: COLUMN_LABEL undefined in aria-label at column select-all checkbox
  • Closed#24188Kanban dashboard tab — rendering error: COLUMN_LABEL is not defined
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.29: 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

Subsystem
hermes cli
Merged PRs
#23791, #33861
Closed issues
2
Attempts
2 PRs closed without merging
Fix link checked
11 May 2026

For your agent

The same facts, typed.