NousResearch/hermes-agent

Fuzzy match deletes boundary space in replacements

Cluster 52491. Data as of

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

The short answer

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

A bug where whitespace-normalized fuzzy matching silently drops the boundary space after a match during str_replace or patch operations. Multiple fix attempts address preserving that boundary space in fuzzy replacements.

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.

  • #52678 fix(fuzzy-match): preserve boundary space after whitespace-normalized match (#52491)

    merged to main

    files: tests/tools/test_fuzzy_match.py, tools/fuzzy_match.py

    Show the diff
    • tests/tools/test_fuzzy_match.py
      @@ -43,6 +43,47 @@ def test_extra_spaces_match(self):
               assert count == 1
               assert "bar" in new
       
      +    def test_boundary_space_preserved_after_match(self):
      +        """Regression: whitespace_normalized match ending with a non-space
      +        character must NOT consume the word-boundary space that follows.
      +        https://github.com/NousResearch/hermes-agent/issues/52491"""
      +        # Case 1 — simple word boundary
      +        new, count, strategy, err = fuzzy_find_and_replace(
      +            "foo   bar baz", "foo bar", "XY",
      +        )
      +        assert err is None
      +        assert count == 1
      +        assert strategy == "whitespace_normalized"
      +        assert new == "XY baz", f"Boundary space deleted: {new!r}"
      +
      +    def test_boundary_space_preserved_in_code_edit(self):
      +        """Regression: real-world code-edit scenario where the space before
      +        the next operator must survive a whitespace-normalized match."""
      +        content = "result = compute(a,  b) + tail"
      +        new, count, strategy, err = fuzzy_find_and_replace(
      +            content, "compute(a, b)", "compute(a, b, c)",
      +        )
      +        assert err is None
      +        assert count == 1
      +        assert strategy == "whitespace_normalized"
      +        assert new == "result = compute(a, b, c) + tail", f"Boundary space deleted: {new!r}"
      +
      +    def test_trailing_ws_still_consumed_when_match_ends_with_space(self):
      +        """When the normalized match itself ends with whitespace (pattern has
      +        trailing space), the expansion must still consume the full whitespace
      +        run in the original."""
      +        # Use a pattern with trailing space where the boundary is clear:
      +        # content has "foo   " then "bar", pattern is "foo " — the match
      +        # should cover all 3 original spaces (the trailing ws run).
      +        new, count, strategy, err = fuzzy_find_and_replace(
      +            "a = foo   + bar", "foo +", "XY",
      +        )
      +        assert err is None
      +        assert count == 1
      +        # "foo   +" normalized to "foo +" matches; trailing spaces consumed
      +        # Result: "a = XY bar"
      +        assert "XY" in new and "bar" in new
      +
       
       class TestIndentDifference:
           def test_different_indentation(self):
    • tools/fuzzy_match.py
      @@ -768,9 +768,14 @@ def _map_normalized_positions(original: str, normalized: str,
               else:
                   orig_end = orig_start + (norm_end - norm_start)
               
      -        # Expand to include trailing whitespace that was normalized
      -        while orig_end < len(original) and original[orig_end] in ' \t':
      -            orig_end += 1
      +        # Expand to include trailing whitespace that was normalized,
      +        # but only when the normalized match itself ended with whitespace.
      +        # When the match ends with a non-space character, the first
      +        # whitespace in the original is a word boundary and must not be
      +        # consumed.  See https://github.com/NousResearch/hermes-agent/issues/52491
      +        if norm_end < len(normalized) and normalized[norm_end - 1] == ' ':
      +            while orig_end < len(original) and original[orig_end] in ' \t':
      +                orig_end += 1
               
               original_matches.append((orig_start, min(orig_end, len(original))))
           

3 PRs closed without merging

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

  • #52502 fix(fuzzy-match): preserve boundary space after whitespace-normalized match (#52491) · other → #52678
  • #52513 fix(tools): preserve word-boundary space in whitespace-normalized edits · superseded by another PR → #52678, #52502
  • #52861 fix(tools): preserve boundary spaces in fuzzy replacements · duplicate → #52678, #52491
Closed issues · 1
  • Closed#52491[Bug]: Fuzzy whitespace-normalized edit silently deletes the boundary space after a match (str_replace / patch corrupts file)
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.40: 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

Merged PRs
#52678
Closed issues
1
Attempts
3 PRs closed without merging
Fix link checked
26 Jun 2026

For your agent

The same facts, typed.