NousResearch/hermes-agent

Sanitizer misclassifies tool results due to whitespace in tool_call_id

Cluster 9999 · agent. Data as of

Nothing open on GitHub. Fix merged in #55519 on 30 Jun 2026. Check it covers your case.

The short answer

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

These issues and fixes address a bug where the run_agent sanitizer incorrectly classifies tool results when the tool_call_id contains surrounding whitespace. The proposed fixes involve normalizing or stripping whitespace from tool_call_id before performing comparisons.

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.

  • #55519 fix: normalize tool_call_id whitespace in sanitizer

    merged to main

    files: agent/agent_runtime_helpers.py, run_agent.py, tests/run_agent/test_agent_guardrails.py

    Show the diff
    • agent/agent_runtime_helpers.py
      @@ -2163,7 +2163,7 @@ def sanitize_api_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]
           result_call_ids: set = set()
           for msg in messages:
               if msg.get("role") == "tool":
      -            cid = msg.get("tool_call_id")
      +            cid = (msg.get("tool_call_id") or "").strip()
                   if cid:
                       result_call_ids.add(cid)
       
      @@ -2172,7 +2172,7 @@ def sanitize_api_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]
           if orphaned_results:
               messages = [
                   m for m in messages
      -            if not (m.get("role") == "tool" and m.get("tool_call_id") in orphaned_results)
      +            if not (m.get("role") == "tool" and (m.get("tool_call_id") or "").strip() in orphaned_results)
               ]
               _ra().logger.debug(
                   "Pre-call sanitizer: removed %d orphaned tool result(s)",
    • run_agent.py
      @@ -3429,8 +3429,8 @@ def _build_system_prompt(self, system_message: str = None) -> str:
           def _get_tool_call_id_static(tc) -> str:
               """Extract call ID from a tool_call entry (dict or object)."""
               if isinstance(tc, dict):
      -            return tc.get("call_id", "") or tc.get("id", "") or ""
      -        return getattr(tc, "call_id", "") or getattr(tc, "id", "") or ""
      +            return (tc.get("call_id", "") or tc.get("id", "") or "").strip()
      +        return (getattr(tc, "call_id", "") or getattr(tc, "id", "") or "").strip()
       
           @staticmethod
           def _get_tool_call_name_static(tc) -> str:
    • tests/run_agent/test_agent_guardrails.py
      @@ -108,6 +108,30 @@ def test_sdk_object_tool_calls(self):
               assert len(out) == 2
               assert out[1]["tool_call_id"] == "c6"
       
      +    def test_tool_result_with_leading_whitespace_preserved(self):
      +        """Tool result IDs with leading/trailing whitespace should match assistant call IDs."""
      +        msgs = [
      +            {"role": "assistant", "tool_calls": [assistant_dict_call("functions.cronjob:24")]},
      +            tool_result(" functions.cronjob:24"),  # leading whitespace
      +        ]
      +        out = AIAgent._sanitize_api_messages(msgs)
      +        # Should NOT inject a stub — the tool result is valid after stripping
      +        assert len(out) == 2
      +        assert out[1]["role"] == "tool"
      +        assert out[1]["content"] == "ok"
      +
      +    def test_truly_orphaned_with_whitespace_still_removed(self):
      +        """Truly orphaned tool results with whitespace should still be removed."""
      +        msgs = [
      +            {"role": "assistant", "tool_calls": [assistant_dict_call("c_valid")]},
      +            tool_result(" c_ORPHAN "),  # whitespace + no matching call
      +        ]
      +        out = AIAgent._sanitize_api_messages(msgs)
      +        assert len(out) == 2  # assistant + stub for c_valid, orphan removed
      +        tool_msgs = [m for m in out if m["role"] == "tool"]
      +        assert len(tool_msgs) == 1
      +        assert tool_msgs[0]["tool_call_id"] == "c_valid"
      +
       
       # ---------------------------------------------------------------------------
       # Phase 2a — _cap_delegate_task_calls

2 PRs closed without merging

From the close comments: done elsewhere 1 · duplicate 1

  • #10000 fix(run-agent): normalize tool_call_id before matching results · done elsewhere → fa3ab2f, #10039, #9999
  • #10432 fix(agent): strip whitespace from tool_call_id in sanitizer · duplicate → #10119
Closed issues · 1
  • Closed#9999run_agent sanitizer misclassifies tool results when tool_call_id has surrounding whitespace
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.50: 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
agent
Merged PRs
#55519
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
30 Jun 2026

For your agent

The same facts, typed.