CherryHQ/cherry-studio · error · Error

old_string and new_string must be different

Error message

old_string and new_string must be different

What it means

Thrown by replaceWithFuzzyMatch (types.ts:518) when oldString === newString. The function performs an edit, so an identical old/new pair is a no-op and is rejected upfront as a caller logic error before any replacer runs. This guards the edit tool against accidental no-op edits that would otherwise silently succeed.

Source

Thrown at src/main/ai/mcp/servers/filesystem/types.ts:525

  WhitespaceNormalizedReplacer,
  IndentationFlexibleReplacer,
  EscapeNormalizedReplacer,
  TrimmedBoundaryReplacer,
  ContextAwareReplacer,
  MultiOccurrenceReplacer
]

/**
 * Replace oldString with newString in content using fuzzy matching
 */
export function replaceWithFuzzyMatch(
  content: string,
  oldString: string,
  newString: string,
  replaceAll = false
): string {
  if (oldString === newString) {
    throw new Error('old_string and new_string must be different')
  }

  let notFound = true

  for (const replacer of ALL_REPLACERS) {
    for (const search of replacer(content, oldString)) {
      const index = content.indexOf(search)
      if (index === -1) continue
      notFound = false
      if (replaceAll) {
        return content.replaceAll(search, newString)
      }
      const lastIndex = content.lastIndexOf(search)
      if (index !== lastIndex) continue
      return content.substring(0, index) + newString + content.substring(index + search.length)
    }
  }

View on GitHub (pinned to 726446b54c)

Solutions

  1. Make new_string actually differ from old_string (even a single character change is enough).
  2. If you only want to verify content exists, use a search/read tool instead of edit.
  3. Add an assertion old !== new in the caller before invoking edit to fail fast with your own message.

Example fix

// before
replaceWithFuzzyMatch(content, 'const x = 1', 'const x = 1') // throws: old_string and new_string must be different

// after
replaceWithFuzzyMatch(content, 'const x = 1', 'const x = 2')
Defensive patterns

Strategy: validation

Validate before calling

function assertDifferentEdit(oldString: string, newString: string): void {
  if (oldString === newString) {
    throw new Error('Refusing no-op edit: old_string equals new_string')
  }
}

Type guard

function isMeaningfulEdit(oldString: string, newString: string): boolean {
  return oldString !== newString
}

Try / catch

try {
  replaceWithFuzzyMatch(content, oldString, newString)
} catch (e) {
  if (e instanceof Error && e.message === 'old_string and new_string must be different') {
    // nothing to do; skip the edit entirely
  } else throw e
}

Prevention

When it happens

Trigger: Calling the edit tool (or replaceWithFuzzyMatch directly) with old_string and new_string that are byte-identical. Common when both are templated from the same source, copy-pasted, or when the caller intends only a whitespace change but normalizes both sides identically first.

Common situations: Caller copies the selection into both old and new fields then forgets to edit the new field; a refactor script derives new from old with no transformation; trailing-newline normalization makes both strings equal; an LLM agent emits the same block twice.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/e82bde49eef264b7. Report an issue: GitHub.