# NousResearch/hermes-agent · Session delete and ID lookup bug corrections

## Open now

- 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.

## TL;DR

- 7 member(s): 0 open issue(s), 3 closed issue(s), 0 open PR(s), 1 merged PR(s), 3 PR(s) closed without merging.
- A fix was merged to main in PR #2102. Verify it addresses your case.
- **Recommended:** read PR #2102 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-2102`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Closed / duplicate issues

- #1412: \[Bug\]: Cannot delete sessions by ID
- #2068: \[Bug\]: Session ID Truncated in \`hermes sessions list\`
- #44117: \[Bug\]: Cannot delete sessions in default proffile / ID not found

## Prior & competing PRs

- **#2102: fix(tools,cli): normalise MCP schemas + expand session list columns**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `hermes_cli/main.py`, `tests/tools/test_mcp_tool.py`, `tools/mcp_tool.py`
  - fix hunks:
  - `hermes_cli/main.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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
     
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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
    ```

    ```diff
    @@ -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.
     
    ```

    ```diff
    @@ -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),
         }
     
     
    ```
- **#44138: fix(desktop): pass owning profile to server on session delete/archive**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `18f429a89b`
- **#44273: fix(cli): resolve session ID before delete like GET/PATCH do (#44117)**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`
- **#83968: fix(desktop): pass session owner to archive and delete endpoints**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`

## Fix status

- Merged to main in PR #2102 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-03-20`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
- Subsystem: `apps/desktop`
