Session delete and ID lookup bug corrections

NousResearch/hermes-agent · cluster 1412 · apps/desktop

Data as of

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

The short answer

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

Multiple bugs around deleting sessions by ID were addressed, including missing owner/profile context passed to endpoints and session ID not being looked up before delete operations. Related changes also cover session listing and MCP schema normalization.

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.

  • #2102 fix(tools,cli): normalise MCP schemas + expand session list columns

    merged to main

    files: hermes_cli/main.py, tests/tools/test_mcp_tool.py, tools/mcp_tool.py

    Show the diff
    • hermes_cli/main.py
      @@ -3721,20 +3721,20 @@ def cmd_sessions(args):
                       return
                   has_titles = any(s.get("title") for s in sessions)
                   if has_titles:
      -                print(f"{'Title':<22} {'Preview':<40} {'Last Active':<13} {'ID'}")
      -                print("─" * 100)
      +                print(f"{'Title':<32} {'Preview':<40} {'Last Active':<13} {'ID'}")
      +                print("─" * 110)
                   else:
                       print(f"{'Preview':<50} {'Last Active':<13} {'Src':<6} {'ID'}")
      -                print("─" * 90)
      +                print("─" * 95)
                   for s in sessions:
                       last_active = _relative_time(s.get("last_active"))
                       preview = s.get("preview", "")[:38] if has_titles else s.get("preview", "")[:48]
                       if has_titles:
      -                    title = (s.get("title") or "—")[:20]
      -                    sid = s["id"][:20]
      -                    print(f"{title:<22} {preview:<40} {last_active:<13} {sid}")
      +                    title = (s.get("title") or "—")[:30]
      +                    sid = s["id"]
      +                    print(f"{title:<32} {preview:<40} {last_active:<13} {sid}")
                       else:
      -                    sid = s["id"][:20]
      +                    sid = s["id"]
                           print(f"{preview:<50} {last_active:<13} {s['source']:<6} {sid}")
       
               elif action == "export":
    • tests/tools/test_mcp_tool.py
      @@ -106,6 +106,18 @@ def test_empty_input_schema_gets_default(self):
               assert schema["parameters"]["type"] == "object"
               assert schema["parameters"]["properties"] == {}
       
      +    def test_object_schema_without_properties_gets_normalized(self):
      +        from tools.mcp_tool import _convert_mcp_schema
      +
      +        mcp_tool = _make_mcp_tool(
      +            name="ask",
      +            description="Ask Crawl4AI",
      +            input_schema={"type": "object"},
      +        )
      +        schema = _convert_mcp_schema("crawl4ai", mcp_tool)
      +
      +        assert schema["parameters"] == {"type": "object", "properties": {}}
      +
           def test_tool_name_prefix_format(self):
               from tools.mcp_tool import _convert_mcp_schema
       
      @@ -1893,6 +1905,33 @@ def test_system_prompt_prepended(self):
               messages = call_args.kwargs["messages"]
               assert messages[0] == {"role": "system", "content": "Be helpful"}
       
      +    def test_server_tools_with_object_schema_are_normalized(self):
      +        """Server-provided tools should gain empty properties for object schemas."""
      +        fake_client = MagicMock()
      +        fake_client.chat.completions.create.return_value = _make_llm_response()
      +        server_tool = SimpleNamespace(
      +            name="ask",
      +            description="Ask Crawl4AI",
      +            inputSchema={"type": "object"},
      +        )
      +
      +        with patch(
      +            "agent.auxiliary_client.call_llm",
      +            return_value=fake_client.chat.completions.create.return_value,
      +        ) as mock_call:
      +            params = _make_sampling_params(tools=[server_tool])
      +            asyncio.run(self.handler(None, params))
      +
      +        tools = mock_call.call_args.kwargs["tools"]
      +        assert tools == [{
      +            "type": "function",
      +            "function": {
      +                "name": "ask",
      +                "description": "Ask Crawl4AI",
      +                "parameters": {"type": "object", "properties": {}},
      +            },
      +        }]
      +
           def test_length_stop_reason(self):
               """finish_reason='length' maps to stopReason='maxTokens'."""
               fake_client = MagicMock()
    • tools/mcp_tool.py
      @@ -605,7 +605,9 @@ async def __call__(self, context, params):
                           "function": {
                               "name": getattr(t, "name", ""),
                               "description": getattr(t, "description", "") or "",
      -                        "parameters": getattr(t, "inputSchema", {}) or {},
      +                        "parameters": _normalize_mcp_input_schema(
      +                            getattr(t, "inputSchema", None)
      +                        ),
                           },
                       }
                       for t in server_tools
      @@ -1213,6 +1215,17 @@ def _check() -> bool:
       # Discovery & registration
       # ---------------------------------------------------------------------------
       
      +def _normalize_mcp_input_schema(schema: dict | None) -> dict:
      +    """Normalize MCP input schemas for LLM tool-calling compatibility."""
      +    if not schema:
      +        return {"type": "object", "properties": {}}
      +
      +    if schema.get("type") == "object" and "properties" not in schema:
      +        return {**schema, "properties": {}}
      +
      +    return schema
      +
      +
       def _convert_mcp_schema(server_name: str, mcp_tool) -> dict:
           """Convert an MCP tool listing to the Hermes registry schema format.
       
      @@ -1231,10 +1244,7 @@ def _convert_mcp_schema(server_name: str, mcp_tool) -> dict:
           return {
               "name": prefixed_name,
               "description": mcp_tool.description or f"MCP tool {mcp_tool.name} from {server_name}",
      -        "parameters": mcp_tool.inputSchema if mcp_tool.inputSchema else {
      -            "type": "object",
      -            "properties": {},
      -        },
      +        "parameters": _normalize_mcp_input_schema(mcp_tool.inputSchema),
           }
       
       

3 PRs closed without merging

From the close comments: superseded by another PR 1 · other 2

  • #44138 fix(desktop): pass owning profile to server on session delete/archive · superseded by another PR → 18f429a
  • #44273 fix(cli): resolve session ID before delete like GET/PATCH do (#44117) · other
  • #83968 fix(desktop): pass session owner to archive and delete endpoints · other
Closed issues · 3
  • Closed#1412[Bug]: Cannot delete sessions by ID
  • Closed#2068[Bug]: Session ID Truncated in `hermes sessions list`
  • Closed#44117[Bug]: Cannot delete sessions in default proffile / ID not found
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.22: 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
apps/desktop
Merged PRs
#2102
Closed issues
3
Attempts
3 PRs closed without merging
Fix link checked
20 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.

For your agent

The same facts, typed.