# NousResearch/hermes-agent · Sanitizer misclassifies tool results due to whitespace in tool_call_id

## Open now

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

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #55519. Verify it addresses your case.
- **Recommended:** read PR #55519 (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: `resolved` · deciding PR(s): `pr-55519`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: 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

- #9999: run_agent sanitizer misclassifies tool results when tool_call_id has surrounding whitespace

## Prior & competing PRs

- **#10000: fix(run-agent): normalize tool_call_id before matching results**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `fa3ab2ffd0f18069b8ef10117d4e1e4e4c5a9bd8`, `#10039`, `#9999`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#55519: fix: normalize tool_call_id whitespace in sanitizer**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/agent_runtime_helpers.py`, `run_agent.py`, `tests/run_agent/test_agent_guardrails.py`
  - fix hunks:
  - `agent/agent_runtime_helpers.py`

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

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

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

    ```diff
    @@ -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
    ```
- **#10432: fix(agent): strip whitespace from tool_call_id in sanitizer**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#10119`

## Fix status

- Merged to main in PR #55519 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-30`.
- Subsystem: `agent`
